【问题标题】:How do I reference a parent from a child object with generic interfaces in C#?如何在 C# 中使用通用接口从子对象引用父对象?
【发布时间】:2012-04-01 07:54:45
【问题描述】:

我有以下接口声明:

interface IOrder<T> where T: IOrderItem
{
     IList<T> Items { get; set; }
}

interface IOrderItem
{
     IOrder<IOrderItem> Parent { get; set; } // What do I put here?
}

我希望列表中的项目具有对标头对象的引用,因此它可以使用标头中的 ID 和其他字段。

在我的具体课程中,它抱怨我没有正确实现“父级”。

class StoreOrder : IOrder<StoreOrderItem>
{
    public IList<StoreOrderItem> Items { get; set; }
}

class StoreOrderItem : IOrderItem
{       
    public StoreOrder Parent { get; set; } // This doesn't satisfy the interface
}

我尝试将 IOrderItem 设置为 IOrderItem&lt;T&gt; 并传入 Parent 类型,但这会导致循环引用,因为 Header 类需要 Item 类类型......我很困惑。

关于如何正确实施的任何建议?

【问题讨论】:

标签: c# .net generics .net-4.0 interface


【解决方案1】:
class StoreOrder : IOrder<StoreOrderItem>
{
    public int Id { get; set; }
}

class StoreOrderItem : IOrderItem
{       
    public IOrder<IOrderItem> Parent { get; set; } // This doesn't satisfy the interface
}

您可能没有专攻 - IOrder&lt;IOrderItem&gt;StoreOrder 更通用

【讨论】:

  • 为什么是 ID?为什么没有IOrder&lt;T&gt;.IList&lt;T&gt;
【解决方案2】:

满足接口的声明:

class StoreOrder : IOrder<StoreOrderItem>
{
    // interface members
    public IList<StoreOrderItem> Items { get; set; }

    // own members
    public int Id { get; set; }
}

class StoreOrderItem : IOrderItem
{
    public IOrder<IOrderItem> Parent { get; set; }
}

要访问自定义成员,您必须进行投射:

class StoreOrderItem : IOrderItem
{
    void Test()
    {
        int id = ((StoreOrder)this.Parent).ID;
    }
}

【讨论】:

    【解决方案3】:

    如果你这样定义你的接口:

    interface IOrder<T> where T : IOrderItem<T>
    {
        IList<T> Items { get; set; }
    }
    interface IOrderItem<T> where T : IOrderItem<T>
    {
        IOrder<T> Parent { get; set; }
    }
    

    然后您可以像这样实现它们以获得您期望的功能:

    class StoreOrder : IOrder<StoreOrderItem>
    {
        public IList<StoreOrderItem> Items { get; set; }
    }
    class StoreOrderItem: IOrderItem<StoreOrderItem>
    {
        public IOrder<StoreOrderItem> Parent { get; set; }
    }
    

    【讨论】:

    • 这更正了编译器错误,但将Parent 属性的类型剥离回IOrder&lt;StoreOrderItem&gt; 而不是StoreOrder,就像提问者正在尝试的那样。所以,如果你有一个StoreOrderItem 并且想要对Parent 做任何事情,除了对IOrder&lt;StoreOrderItem&gt;.Items 属性进行操作,你必须先将Parent 转换为StoreOrder
    • @devgeezer - 你是对的。但是,我的实现将允许任何 IOrder&lt;StoreOrderItem&gt; 实现成为父级。 IOrderItem&lt;T&gt; 需要了解其父级的任何内容都应在 IOrder&lt;T&gt; 接口中实现,因为两者之间的关系是在接口级别而不是实现级别定义的。
    • 好点。我在想我对这个问题的意图太多了。 +1
    【解决方案4】:

    以下是更改接口的解决方案:

    interface IOrder<TOrder, TOrderItem> 
        where TOrderItem : IOrderItem<TOrder>
    {
        IList<TOrderItem> Items { get; set; }
    }
    
    interface IOrderItem<TOrder>
    {
        TOrder Parent { get; set; }
    }
    

    更改StoreOrderStoreOrderItem 以支持接口更改为每个属性添加几个属性以供以后测试:

    class StoreOrder: IOrder<StoreOrder, StoreOrderItem>
    {
        public DateTime Date { get; set; }
        public IList<StoreOrderItem> Items { get; set; }
    }
    
    class StoreOrderItem : IOrderItem<StoreOrder>
    {
        public string ItemName { get; set; }
        public decimal ItemPrice { get; set; }
        public StoreOrder Parent { get; set; }
    }
    

    ...现在创建 StoreOrderStoreOrderItem 实例,并逐步完成它们:

    void Main()
    {
        var so = new StoreOrder { Date = DateTime.Now };
        var item = new StoreOrderItem {
                Parent = so,
                ItemName = "Hand soap",
                ItemPrice = 2.50m };
        so.Items = new [] { item };
    
        Console.WriteLine(item.Parent.Date);
        Console.WriteLine(so.Items.First().ItemName);
    }
    

    ...运行时,打印:

    3/16/2012 10:43:55 AM
    Hand soap
    

    另一种选择是废弃上述内容并采用 this solution 并通过添加具有所需类型的 Parent 属性并使用显式接口实现来避免在调用站点进行强制转换来更改它,从而使 StoreOrderItem 实现类似于这个:

    class StoreOrderItem : IOrderItem
    {
        public string ItemName { get; set; }
        public decimal ItemPrice { get; set; }
        public StoreOrder Parent { get; set; } // note: original implementation
    
        IOrder<IOrderItem> IOrderItem.Parent {  // explicit interface implementation
            get { return (IOrder<IOrderItem>)this.Parent; }
            set { this.Parent = (StoreOrder)value; }
        }
    }
    

    我最喜欢上面的第一个提案,上面有两个泛型参数IOrderIOrderItem 上的无约束泛型参数。我发布并现在编辑的先前版本具有两个接口,每个接口都具有相同的两个泛型类型,每个具有相同的约束。我觉得这有点过火了,所以我将其缩减为上述实现。尽管 TOrder 类型参数对 IOrderItem 完全没有约束 - 尝试在其位置上捏造其他类型(例如,object)会导致编译错误。在没有类型约束的情况下,使用TOrder 而不是仅仅调用它T 提供了关于预期类型的​​提示。那将是我的最后一次编辑——我觉得这是我尝试中最简洁的一次;如果您好奇,我可以提供在接口上具有双泛型约束类型的前一个实现,但这至少是我更喜欢的解决方案。干杯!

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-11-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多