【问题标题】:Cast drops Implemented Member array in C#Cast drop 在 C# 中实现的成员数组
【发布时间】:2015-03-18 17:19:00
【问题描述】:

考虑:

class Order
{
    public List<OrderItem> OrderItems {get;set;}
}

class SalesOrder : Order
{
    public List<SalesOrderItem> OrderItems {get;set;}
}

class OrderItem {}

class SalesOrderItem : OrderItem {}

尝试投射时

SalesOrder salesorder = new SalesOrder();
salesorder.OrderItems = List<SalesOrderItem>();
salesorder.OrderItems.Add(new SalesOrderItem());
Order order = salesorder;

order.Orderitems 为空。有什么方法可以让这个演员与派生属性一起工作?

我已尝试使用接口进行协方差,但无法使用 List 并且使用 IEnumerable 将不允许公共设置器。关于如何实现这一点的任何想法?

【问题讨论】:

  • 这不会编译 - 似乎您在 OrderItemsOrderItems 属性上缺少 new SalesOrder...
  • @AlexeiLevenkov,我不认为这是一个骗局。 OP 可能只是没有显示属性初始化代码。问题好像是关于OrderItems继承,跟NRE没有关系
  • 这里的问题是编译器不会将两个版本的 OrderItems 视为同一事物。继承不是那样工作的。即使是新的,演员表仍然无法按照您想要的方式工作。
  • @ChrisMantle 这是编译器警告,而不是错误。应该是国际海事组织的错误 - 它应该迫使你承认你真的在做new
  • 我会有这样的dotnetfiddle.net/HtvDBB

标签: c# inheritance casting


【解决方案1】:

这就是所谓的“shadowing”。

您的salesOrder 有两个OrderItems 属性- 一个只对SalesOrders 可见,一个只对Orders 可见。子类中的OrderItems 属性隐藏了父类中的属性。您正在将一个项目添加到一个项目,然后观察另一个项目。

我认为您需要重新考虑您的设计,但您可以通过从子类中删除属性来解决此问题:

class Order
{
    public Order()
    {
        OrderItems = new List<OrderItem>();
    }
    public List<OrderItem> OrderItems {get;set;}
}

class SalesOrder : Order { }

class OrderItem {}

class SalesOrderItem : OrderItem {}

另一种选择是将Order 设为泛型类:

class Order<T> where T: OrderItem
{
    public Order()
    {
        OrderItems = new List<T>();
    }
    public List<T> OrderItems {get;set;}
}

class SalesOrder : Order<SalesOrderItem> { }

class OrderItem {}

class SalesOrderItem : OrderItem {}
SalesOrder salesorder = new SalesOrder();
salesorder.OrderItems.Add(new SalesOrderItem());
Order<SalesOrderItem> order = salesorder;

第二种方法的好处是它允许OrderItems 存储特定类型的订单项目,而不是最通用的类​​型。

【讨论】:

  • 注意:这会使SalesOrder.OrderItems 中的项目具有OrderItem 类型,因此您必须将它们大小写为SalesOrderItem
  • 最后一行需要读 Order order = salesorder;我试过这个解决方案,C# 编译器不会强制转换
  • @Franklin 是的,这是有充分理由的。如果您想在SalesOrder 中获取所有OrderItems,可以使用IEnumerable&lt;OrderItem&gt; orderItems = salesOrder.OrderItems.Cast&lt;OrderItem&gt;();,但不能将Order&lt;SalesOrderItem&gt; 转换为Order&lt;OrderItem&gt;。它会违反类型系统。出于同样的原因,您不能将 List&lt;SalesOrderItem&gt; 转换为 List&lt;OrderItem&gt;
  • 看起来我将使用第二个示例并编写一些映射代码来进行伪“强制转换”
【解决方案2】:

您的代码中有几个错误。您似乎混淆了一些 OO 概念,以及它们在 C# 中的实现方式。

在您的代码中,您试图从Order 派生SalesOrder。这是最重要的错误:它违反了Liskov substitution principle。特别是,这意味着派生类不能为其成员的签名添加额外的约束。请注意,您的SalesOrder确实添加了这样一个约束:订单项必须是SalesOrderItem 类型,而Order 类接受从OrderItem 派生的所有订单项。

您想要的是专业化:SalesOrder 类比 Order 类更严格:它只能包含 SalesOrderItems。专业化是通过使用泛型来实现的。所以你可以定义你的类如下:

public class Order<T> where T: OrderItem
{
    public List<T> OrderItems { get; set; }
}

public class OrderItem
{
}

public class SalesOrder : Order<SalesOrderItem>
{
}

public class SalesOrderItem : OrderItem
{
}

更新:为了说明为什么无法将SalesOrder 转换为Order&lt;OrderItem&gt;,请考虑以下静态方法:

public static void AddOrderItem(Order<OrderItem> order)
{
    var item = new OrderItem();
    order.OrderItems.Add(order);
}

如果SalesOrder 是从Order&lt;OrderItem&gt; 派生的,你可以这样写:

var order = new SalesOrder();
AddOrderItem(order);

但是,这会导致类型错误,因为 SalesOrder 不能包含普通的 OrderItem。现在,如果您将SalesOrder 转换为Order 的唯一原因是读取订单项,那么您可以引入一个接口:

public interface IOrder
{
    IEnumerable<OrderItem> GetItems();
}

public class Order<T> : IOrder where T: OrderItem
{
    public List<T> OrderItems { get; set; }

    public IEnumerable<OrderItem> GetItems()
    {
        return this.OrderItems;
    }
}

现在您可以将您的 SalesOrder 对象转换为 IOrder 并读取订单项。

【讨论】:

  • 这是我在尝试问题中的解决方案之前的确切解决方案。我仍然无法将 SalesOrder 转换为 Order - 另外,感谢您指出我的 Liskov 违规行为是正确的
猜你喜欢
  • 2018-03-29
  • 1970-01-01
  • 2023-03-08
  • 1970-01-01
  • 2021-06-02
  • 1970-01-01
  • 2018-10-12
  • 2012-11-04
  • 2013-09-25
相关资源
最近更新 更多