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



Question:

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.


Solution:1

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.


Solution:2

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.


Solution:3

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();      }  }  


Solution:4

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 { ... }  


Solution:5

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.


Solution:6

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 :(


Solution:7

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.


Solution:8

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.


Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
Previous
Next Post »