列表泛型——隐式强制转换



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

我可以声明这个:

Bob b = new SubBob();

但是我不能这样做:

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

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

是否有一种方法可以进行隐式转换,或者需要强制转换?

简短回答

通过实例化BindingList<SubBob>,您将其限制为使用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!

myListCat s的列表,你希望它如何处理Dog ?

编译器不会知道有问题,会很高兴地编译你的代码。当这段代码运行时应该发生什么?一个例外吗?而引入泛型正是为了解决类型安全问题。

Co-和逆变的旁注

在。net 4.0中,为委托和接口(而不是类)添加了通用协方差和逆变性,这是正确的。例如,IEnumerable<out T>是协变的,这意味着您可以将它赋值给任何类型的变量派生的小于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<out T>保证它只返回 T(关键字out)和从不接受它。如果它接受T作为参数,它将打开上述问题的大门。因此,ICollection 不是协变。

以类似的方式,一些接口保证它们只接受 T(关键字in),并且从不返回它。这样的接口被称为逆变,允许用赋值给更具体的 T: 变量。
IComparer<Animal> animalComparer = // ...
IComparer<Dog> dogComparer = animalComparer; // comparer of animals is comparer of dogs

关于您的情况:
这里处理的是逆变,类只能支持协方差或逆变IEnumerable<T>支持协方差

它的签名是IEnumerable<out T>,所以它支持继承T中类的类。

一般

:
不幸的是,这在。net 3.5中不可用。

它在。net 4中作为协方差和逆变引入。

在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;

是否有一种方法可以进行隐式转换,或者是强制转换需要吗?

下面的代码应该允许任何继承了Bob的类作为Bob添加到列表中。

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

你到底想干什么?

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

BindingList<Bob>已经可以包含SubBob对象(以及从Bob派生的任何其他对象),因此实际上可能不需要BindingList<SubBob>。如果由于某种原因,您需要同时使用这两个接口,那么您唯一的"简单"选择可能是使用非通用的IBindingList接口。

。. NET 4支持接口协方差,它允许将只读泛型接口向下转换为具有基类的相同接口,因此它可能对您的情况有帮助,也可能没有帮助……也就是说,如果你没有3.5:

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

如果你坚持使用。net 3.5并且不能改变你的数据访问层来提供一些更方便的东西,你可以使用一个相当脏的转换:

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

除了平庸的设计之外,这还引入了从IEnumerable<>ToList()重建列表的成本。另一方面,BindingList<>构造函数只包装列表。

最新更新