【问题标题】:How to use a class's type as the type argument for an inherited collection property in C#如何使用类的类型作为 C# 中继承的集合属性的类型参数
【发布时间】:2012-09-24 05:59:23
【问题描述】:

我正在尝试创建从通用卡片类继承的各种卡片的表示形式,并且所有卡片都包含对其拥有的牌组的引用。

我尝试按照here 的建议重新声明它们,但它仍然无法转换为特定的卡类型。

我目前的代码是这样的:

public class Deck<T> : List<T> 
    where T : Card 
{
    void Shuffle()
    {
        throw new NotImplementedException("Shuffle not yet implemented.");
    }
}

public class Card
{
    public Deck<Card> OwningDeck { get; set; }
}

public class FooCard : Card
{
    public Deck<FooCard> OwningDeck
    {
        get
        {
            return (Deck<FooCard>)base.OwningDeck;
        }
        set
        {
            OwningDeck = value;
        }
    }
}

我得到的编译时错误: 错误2 无法将类型Game.Cards.Deck&lt;Game.Cards.Card&gt; 转换为Game.Cards.Deck&lt;Game.Cards.FooCard&gt;

还有一个警告,建议我使用新的运算符来指定隐藏是故意的。这样做会违反惯例吗?有没有更好的办法?

我对 stackoverflow 的问题是:我想做的事情可以在 .NET 类型系统中优雅地完成吗?如果有,可以举一些例子吗?

【问题讨论】:

  • 我会让卡片成为简单的不可变对象,没有反向引用。
  • 如果可能,派生类将违反 Liskov 替换原则,因为 Card 的任何消费者都希望 FooCard 对象的行为类似于 Card 类型,因此 OwningDeck 的类型为 Deck。使用“new”不会解决您的问题,因为 Card.OwningDeck 将在该类上被破坏,或者 OwningDeck 将意味着两种不同的事情,具体取决于您将对象视为 Card 还是 FooCard (我不确定哪个会发生)。

标签: c# .net inheritance covariance type-systems


【解决方案1】:

您可以为您的卡片配备一个通用参数,该参数指定您正在使用的卡片的基类:

public class Card<TCard>
    where TCard : Card<TCard>
{
    public Deck<TCard> OwningDeck { get; set; }
}

您的 FooCard 类将如下所示:

public class FooCard : Card<FooCard>

与当前代码相比,一个优势可能是您不必重新声明 OwningDeck 属性;它在FooCard 中自动属于Deck&lt;FooCard&gt; 类型。

【讨论】:

    【解决方案2】:

    你在这里遇到的是一个常见的设计错误,你试图将属性“OwningDeck”专门化为 FooCard,这是“隐藏”“OwningDeck”的基本声明。 您所做的选择,将“OwningDeck”放在 Card 类中,旨在确保所有类型的 Card 都具有 OwningDeck 属性 - 这也意味着该属性必须是最低公分母类型(即“Deck” ')。

    因此,您不能在派生类型(即 FooCard)中重新声明此属性,因为您随后试图更改该属性的类型 - 这是一个糟糕的设计选择 - 而 .net 实际上并不支持。

    您收到警告的原因是,Deck&lt;FooCard&gt; OwningDeck 属性隐藏了Deck&lt;Card&gt; OwningDeck 属性。如果有人将变量转换为Card 类型,然后访问OwningDeck 属性,他们将从OwningDeck 中获取该属性。但是,如果他们将其转换为FooCard,然后访问相同的属性,他们将获得从Deck&lt;FooCard&gt; OwningDeck 返回的值。编译器不知道这些可能正在访问同一个变量,但这是非常危险的,因为程序员不会期望您的代码有这种行为。

    就更好的实现而言我看到了 O.R. Mapper 在实际实施中击败了我。他说了什么;)

    【讨论】:

      【解决方案3】:

      如果您愿意将 Card 类设为通用类,则可以实现与您正在寻找的目标相近的目标,如下所示:

      public class Deck<T> : List<Card<T>> 
          where T : Card<T>
      {
          void Shuffle()
          {
              throw new NotImplementedException("Shuffle not yet implemented.");
          }
      }
      
      public class Card<T> where T : Card<T>
      {
          public Deck<T> OwningDeck { get; set; }
      }
      
      public class FooCard : Card<FooCard>
      {
      }
      

      原始代码不起作用的原因是Deck&lt;Card&gt;Deck&lt;FooCard&gt; 不同,反之亦然。想象一下:

      Deck<Card> deck = new Deck<Card>();
      deck.Add(new BarCard());
      Deck<FooCard> fooDeck = (Deck<FooCard>)deck;  // What should happen here?
      FooCard foo = deck[0];                        // or here?
      

      所以你需要问自己,“为什么Card 班会关心哪个牌组拥有它?”在我知道的大多数程序中,卡片没有理由知道这些细节。如果您出于特定原因希望它了解其Deck,那么它真的需要了解多少Deck?例如,Deck 是否有可能容纳来自各种不同Deck 类型的卡片?当您从牌库中取出卡片时,您真的需要知道它们与您当前的卡片类型完全相同吗?

      一旦你回答了这些问题,你就会更愿意决定 OwningDeck 应该是 Deck&lt;T&gt; 还是 Deck&lt;T&gt; 实现的某个协变或逆变接口,或者它是否属于 @ 987654333@ 上课。

      【讨论】:

      • 感谢您的洞察力。我非常感谢您指出无法编译的原因的代码示例。我将重新评估我的设计选择并重新实现它。再次感谢您的意见。
      猜你喜欢
      • 1970-01-01
      • 2015-11-18
      • 2010-09-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多