Как вы используете foreach в базовом классе, используя дженерики? - PullRequest
2 голосов
/ 03 июня 2009

Этот вопрос является результатом попытки ответа Джона Скита на этот вопрос .

Итак, у меня есть следующий код, основанный на вопросе и ответе по приведенной выше ссылке на вопрос.

 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())
    {

    }
}

Я пытаюсь понять дженерики больше, чтобы улучшить свой набор навыков, поэтому у меня вопрос: Теперь, как я могу использовать foreach для циклического перебора коллекции "Orders"? Я использую C # 2.0.

Код Пример того, что я пытаюсь сделать (не компилируется).

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

РЕДАКТИРОВАТЬ: Добавлен OverseasOrder, чтобы дать лучший пример.

Ответы [ 8 ]

6 голосов
/ 03 июня 2009

Проблема здесь в том, что Order является универсальным классом, поэтому Order<T> и Order - это два разных типа.

Вы можете сделать это:

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

Или ... вы можете переопределить Order, чтобы он не являлся универсальным, и в этом случае вы потеряете строгую типизацию.

2 голосов
/ 03 июня 2009

В вашем случае нет такой вещи, как класс Order, только класс Order .

Если вы хотите работать с заказами в общем случае без знания какого-либо определенного подтипа (или универсального параметра), вам нужно абстрагировать то, что вам нужно, в базовый класс или интерфейс и оформить Delivery.ToString с помощью метода в новом класс заказа.

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

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

    public override string DeliveryString() {
        return Delivery.ToString();
    }
}
2 голосов
/ 03 июня 2009

В общем, здесь вы ошибаетесь. Поскольку типы для дженериков готовятся во время компиляции, вам нужно указать тип ордера, для которого вы делаете список, с типом.

Он будет скомпилирован, если вы добавите типы как таковые:

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

Может быть, это уже не то, что вы хотели, но вам нужно пойти по-другому, если вам нужно установить это без предопределенных типов.

1 голос
/ 03 июня 2009

Order<T> - абстракция времени компиляции. Order будет абстракцией времени выполнения, но в вопросе нет Order.

Так как Order нет, List<Order> нет. Существуют такие коллекции:

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

Предположим, у вас есть эта коллекция:

ArrayList myOrders = GetOrders();

Затем вы можете выполнить итерацию по коллекции и получить экземпляры CustomerOrder следующим образом:

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

Таким же образом, если у вас есть List<Order<ShippingContainer>>, вы можете получить из него экземпляры OverseasOrder.

В .Net Framework 2.0 этот метод работает с абстракциями во время выполнения. В .Net Framework 3.5 вместо фильтрации можно было бы вызвать Enumerable.OfType<T>.

1 голос
/ 03 июня 2009

Вы не можете просто сказать Порядок, вы должны сказать Порядок чего (т. Е. Order<???>) Это должно работать:

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

Однако проблема в том, что .NET не поддерживает универсальный полиморфизм, поэтому вы не можете просто определить свои переменные как Order<DeliveryStrategy> и ожидать, что они будут работать: (

1 голос
/ 03 июня 2009

List<Order> не является допустимым типом, потому что Order не является допустимым типом - вам нужен Order<T>. За исключением того, что вы не знаете T.

Две возможности:

Вариант 1: оберните foreach в общий метод:

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

Вариант 2: Возможно, вы не знаете, какой у вас тип ордера. Вы действительно хотите "Порядок чего угодно". Были предположения о добавлении этого в C # - предполагаемая функция называется " mumble types ", как в "Order of (mumble mumble) " Но в отсутствие такой языковой функции вам нужен какой-то конкретный класс, из которого вы можете составить список.

Итак, в вашем примере вы могли бы создать интерфейс с именем IOrder или базовый класс (возможно, абстрактный) с именем Order или OrderBase. В любом случае вы не можете поместить свойство Delivery в этот базовый класс / интерфейс, потому что вы не знаете, каким будет тип Delivery, поэтому вам придется добавить другой метод или свойство в IOrder. Например, вы можете поместить свойство DeliveryAsString в IOrder.

1 голос
/ 03 июня 2009

Но у вас нет Order класса ... У вас есть Order<T> класс. Вот почему вы не можете иметь List<Order> класс.

Если вы хотите иметь базовый класс для всех ордеров, которые вы теперь можете использовать в списках, попробуйте этот способ:

public abstract class OrderBase{ ... }
...
public abstract class Order<TDelivery>:OrderBase where TDelivery : DeliveryStrategy { ... }
0 голосов
/ 03 июня 2009

Для повторения заказа вы можете использовать его следующим образом ...

orders.ForEach (делегат (Order orderItem) { // поэтому некоторая обработка });

Если вы не хотите использовать анонимный метод, напишите метод, сделайте это.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...