【问题标题】:List Generics - implicit casting列表泛型 - 隐式转换
【发布时间】:2011-08-01 12:43:43
【问题描述】:

我的目标是 .NET 3.5。假设我有一个类 Bob,它是 SubBob 的抽象基类。

我可以这样声明:

Bob b = new SubBob();

但我不能这样做:

 // compliation error - can't convert
BindingList<Bob> myList = new BindingList<SubBob>(); 

我的猜测是 BindingList 不希望您这样做,因为它必须知道右侧的类型与左侧的内存布局相同。 SubBob 的大小可能比 Bob 大。

有什么方法可以进行隐式转换,还是需要强制转换?

【问题讨论】:

  • 您使用的是 Visual Studio 2010 吗?
  • @Daniel - 他使用什么版本的 VS 有什么关系?他已经说过他的目标是 3.5,这意味着 C# 3.0 是他将使用的版本。
  • 不,将使用4.0编译器。
  • 下一行:myList.Add(new SomethingOtherThanASubBobButThatAlsoDerivesFromBob())。对于这个只希望包含 SubBob 实例的列表,您看到问题出在哪里了吗?
  • @Daniel White,每个项目都使用目标框架指定。 VS2010 允许您针对 4.0 之后的任何现有框架

标签: .net generics casting implicit-conversion


【解决方案1】:

简短的回答

通过实例化BindingList&lt;SubBob&gt;,您可以将其限制为与SubBob更具体的 类型(例如SubSubBob)一起使用。

如果您希望Bob 也适合那里,请将myList 声明为您想要支持的最不具体的类型

BindingList<Bob> myList = new BindingList<Bob>(); 

(或者,更方便的是)

var myList = new BindingList<Bob>(); 

说明

这与内存无关(BindingList 仅包含对对象的引用,并且所有引用的大小相同),而是与您将引入的逻辑不一致有关。

如果这样的代码是可能的,你将能够任意打破类型限制

BindingList<Animal> myList = new BindingList<Cat>(); 
myList.Add(new Dog()); // bang!

myListCats 的列表,您希望它如何处理Dog

编译器不会知道有问题,并且会很高兴地编译您的代码。这段代码运行时会发生什么?例外?但是泛型的引入正是为了解决类型安全问题。

关于协变和逆变的附注

在 .NET 4.0 中,为委托和接口添加了generic covariance and contravariance 是正确的(不适用于类)。例如,IEnumerable&lt;out T&gt; 是协变的,这意味着您可以将其分配给任何类型的变量T 更少派生

IEnumerable<Cat> cats = new List<Cat> { new Cat("Hudson"), new Cat("Crookshanks") };
IEnumerable<Animal> animals = cats; // sequence of cats is sequence of animals

但这只是可能的,因为IEnumerable&lt;out T&gt; 保证它只返回 T(关键字out)并且从不接受它。如果它接受T 作为参数,它将打开上述问题的大门。因此,ICollection协变的。

以类似的方式,一些接口保证他们只接受T(关键字in)并且永不返回它。这样的接口被称为逆变,并允许分配给具有更具体T的变量:

IComparer<Animal> animalComparer = // ...
IComparer<Dog> dogComparer = animalComparer; // comparer of animals is comparer of dogs

【讨论】:

    【解决方案2】:

    具体针对您的情况
    你在这里处理逆变,一个类只能支持协变或逆变IEnumerable&lt;T&gt; 支持协方差。

    它的签名是IEnumerable&lt;out T&gt;,所以它支持继承T中的类。

    一般情况
    不幸的是,这在 .NET 3.5 中不可用。

    它在 .NET 4 中以Covariance and Contravariance 的形式引入。

    a FAQ on the subject at MSDN。摘录如下:

    // Assignment compatibility. 
    string str = "test";
    // An object of a more derived type is assigned to an object of a less derived type. 
    object obj = str;
    
    // Covariance. 
    IEnumerable<string> strings = new List<string>();
    
    // Contravariance.           
    // Assume that I have this method: 
    // static void SetObject(object o) { } 
    Action<object> actObject = SetObject;
    

    【讨论】:

    • 即便如此,对于 List&lt;T&gt; 的具体示例,它也无济于事。
    • 达米安是正确的。当我以 .NET 4 为目标时,如果变量的类型为 IEnumerable,协方差就会起作用。由于某种原因,这不适用于 BindingList,即使 BindingList 是 IEnumerable 和 IList 的衍生物
    • -1 因为协变和逆变与所描述的问题关系不大。 BindingList&lt;T&gt; 是一个类,而不是一个接口,底层的IBindingList 根本不是通用的。 ICollection&lt;T&gt; 不支持类型变化,IEnumerable&lt;T&gt; 是不可能的,因为我们想要 Add 项目。真正的问题是没有实际需要myList 声明为@987654334 @ 因为作者似乎打算让它表现得像BindingList&lt;Bob&gt;
    • @Stealth:您可以使用IEnumerable 协方差,但只能在枚举上扩展。 IList 既不是协变的也不是逆变的。
    • @Dan 我很确定“BindingList”继承了“IEnumerable”,因此实际上具有上述协方差。 'Add' 方法指向相同的 'T' 并考虑协方差。
    【解决方案3】:

    有没有办法可以进行隐式转换,或者是强制转换 需要吗?

    以下内容应该允许任何继承 Bob 的类作为 Bob 添加到列表中。

    BindingList<Bob> myList = new BindingList<Bob>();  
    

    你到底想做什么?

    【讨论】:

    • 我正在尝试存储一个 BindingList 其中 T 是用于子类的基类类型。我的数据访问层当前返回显式列表类型,即 BindingList(),但在这种情况下 GUI 并不关心子类型。
    • “你甚至问这种问题的事实告诉我你是 C# 的新手。”你的意思是什么(这是“你的”的正确用法)?我提出问题是因为我需要帮助并且正在寻找答案。
    【解决方案4】:

    您的代码不起作用的原因是泛型不继承其参数的层次结构:BindingList&lt;SubBob&gt; 不是从BindingList&lt;Bob&gt; 派生的。这与他们的内存布局无关。

    BindingList&lt;Bob&gt; 已经可以包含SubBob 对象(以及从Bob 派生的任何其他对象),因此实际上可能不需要BindingList&lt;SubBob&gt;。如果出于某种原因您需要两者的原因,您唯一的“简单”选项可能是使用非通用IBindingList 接口。

    .NET 4 支持接口covariance,它允许将只读通用接口下转换为与基类相同的接口,因此它可能对您的情况有帮助,也可能没有帮助...也就是说,如果您没有坚持 3.5:

    IEnumerable<Bob> myList = new BindingList<SubBob>();
    

    【讨论】:

    • 我很欣赏你如何把它放在三个小段落中。
    【解决方案5】:

    如果您受困于 .NET 3.5 并且无法更改数据访问层以提供更方便的功能,您可以使用相当 dirty 转换:

    BindingList<SubBob> subBobs = new BindingList<SubBob>;
    BindingList<Bob> bobs = new BindingList(subBobs.Cast<Bob>().ToList())
    

    除了平庸的设计,这引入了从IEnumerable&lt;&gt; 重构列表的成本ToList()。另一方面,BindingList&lt;&gt; 构造函数只包装列表。

    【讨论】:

      猜你喜欢
      • 2011-09-12
      • 1970-01-01
      • 1970-01-01
      • 2021-03-15
      • 1970-01-01
      • 1970-01-01
      • 2017-09-10
      • 2018-12-08
      • 1970-01-01
      相关资源
      最近更新 更多