Crispy
Crispy

Reputation: 5637

How do you use foreach on a base class using generics?

This question is as a result of trying Jon Skeet's answer to this question.

So I have the following code based in the question and answer from the above question link.

 public abstract class DeliveryStrategy { }
public class ParcelDelivery : DeliveryStrategy { }
public class ShippingContainer : DeliveryStrategy { }

public abstract class Order<TDelivery> where TDelivery : DeliveryStrategy
{
    private TDelivery delivery;

    protected Order(TDelivery delivery)
    {
        this.delivery = delivery;
    }

    public TDelivery Delivery
    {
        get { return delivery; }
        set { delivery = value; }
    }
}

public class CustomerOrder : Order<ParcelDelivery>
{
    public CustomerOrder()
        : base(new ParcelDelivery())
    { }
}

public class OverseasOrder : Order<ShippingContainer>
{
    public OverseasOrder() : base(new ShippingContainer())
    {

    }
}

I'm trying understand generics more to improve my skill set, so I have the question of: Now how can I use foreach to loop through a collection of "Orders"? I'm using C#2.0.

Code Example of what I'm trying to do (does not compile).

List<Order> orders = new List<Order>();

orders.Add(new CustomerOrder());
orders.Add(new CustomerOrder());
orders.Add(new OverseasOrder());
orders.Add(new OverseasOrder());

foreach (Order order in orders)
{
     order.Delivery.ToString();
}

EDIT: Added OverseasOrder to give a better example.

Upvotes: 2

Views: 7977

Answers (8)

Adam Sills
Adam Sills

Reputation: 17062

In your case, there is no such thing as an Order class, only an Order<TDelivery> class.

If you want to work on orders generically without knowledge of any specific sub type (or generic parameter) you need to abstract out what you need into a base class or an interface and facade the Delivery.ToString with a method on the new order class.

abstract class Order {
    public abstract string DeliveryString();
}

abstract class Order<TDelivery> : Order {
    // methods omitted for brevity

    public override string DeliveryString() {
        return Delivery.ToString();
    }
}

Upvotes: 2

Amy B
Amy B

Reputation: 110101

Order<T> is a compile time abstraction. Order would be a runtime abstraction, but there is no Order in the question.

Since there is no Order, there is no List<Order>. There are these kinds of collections:

List<Order<ParcelDelivery>>
List<CustomerOrder>
List<Order<ShippingContainer>>
List<OverseasOrder>
ArrayList

Suppose you have this collection:

ArrayList myOrders = GetOrders();

Then you could iterate through the collection and get the CustomerOrder instances this way:

foreach(object someThing in myOrders)
{
  CustomerOrder myOrder = someThing as CustomerOrder;
  if (myOrder != null)
  {
    //work with myOrder
  }
}

In the same way, if you have a List<Order<ShippingContainer>> you can get the OverseasOrder instances from it.

In .Net Framework 2.0, this technique is how one works with runtime abstractions. In .Net Framework 3.5, one would call Enumerable.OfType<T> to perform the filtering instead.

Upvotes: 1

Andriy Volkov
Andriy Volkov

Reputation: 18923

You can't just say Order, you must say Order of what (i.e. Order<???>) This should work:

List<Order<ParcelDelivery>> orders = new List<Order<ParcelDelivery>>();

orders.Add(new CustomerOrder());
orders.Add(new CustomerOrder());
orders.Add(new CustomerOrder());
orders.Add(new CustomerOrder());
orders.Add(new CustomerOrder());

foreach (Order<ParcelDelivery> order in orders)
{
    order.Delivery.ToString();
}

The problem however is that .NET does not support generic polymorphism so you can't just define your variables as Order<DeliveryStrategy> and expect them to work :(

Upvotes: 1

Joe White
Joe White

Reputation: 97696

List<Order> isn't a valid type because Order isn't a valid type -- you need an Order<T>. Except you don't know the T.

Two possibilities:

Option 1: Wrap your foreach inside a generic method:

public void WriteOrders<T>(IList<Order<T>> orders)
{
    foreach (Order<T> order in orders)
        Console.WriteLine(order.Delivery.ToString());
}

Option 2: Maybe you don't know what type of Order you'll actually have. You really want an "Order of whatever". There's been speculation on adding this to C# -- the speculated feature is called "mumble types", as in "Order of (mumble mumble)". But in absence of such a language feature, you need to have some concrete class that you can make a List of.

So in your example, you could make an interface called IOrder, or a base class (perhaps abstract) called Order or OrderBase. In either case, you can't put a Delivery property on that base class/interface, because you don't know what type Delivery is going to be, so you would have to add another method or property to IOrder. For example, you could put a DeliveryAsString property on IOrder.

Upvotes: 1

Peanut
Peanut

Reputation: 19407

To iterate Order you could use it like this ...

orders.ForEach(delegate(Order orderItem) { //so some processing });

If you don't want to use the anonymous method then write a method so do this.

Upvotes: 0

Massimiliano
Massimiliano

Reputation: 16980

But you don't have any Order class... You have Order<T> class. That's why you can't have List<Order> class.

If you want to have a base class for all orders that you can now use in lists, you should try this way:

public abstract class OrderBase{ ... }
...
public abstract class Order<TDelivery>:OrderBase where TDelivery : DeliveryStrategy { ... }

Upvotes: 1

David Hay
David Hay

Reputation: 3047

The generic is where you're going wrong here. Since the types for generics are cooked up at compile time, you need to indicate what kind of Order you're making the list for with a type.

It will compile if you add types as such:

List<Order<ParcelDelivery>> orders = new List<Order<ParcelDelivery>>();

        orders.Add(new CustomerOrder());
        orders.Add(new CustomerOrder());
        orders.Add(new CustomerOrder());
        orders.Add(new CustomerOrder());
        orders.Add(new CustomerOrder());

        foreach (Order<ParcelDelivery> order in orders)
        {
            order.Delivery.ToString();
        }

Maybe this is no longer what you intended, but you're have to go about it a different way if you need to be able to set this up without predetermined types.

Upvotes: 2

Lasse V. Karlsen
Lasse V. Karlsen

Reputation: 391326

The problem here is that Order is a generic class, so Order<T> and Order is basically two distinct types.

You can do this:

public abstract class Order
{
    public abstract String GetDeliveryMethod();
}

public abstract class Order<TDelivery> : Order
    where TDelivery : DeliveryStrategy
{
    .... the rest of your class

    public override String GetDeliveryMethod()
    {
        return Delivery.ToString();
    }
}

Or... you can redefine Order to be non-generic, in which case you'll loose some strong typing.

Upvotes: 6

Related Questions