为什么IEnumerable <t>只有“out”协变标志而不是“in”c#?</t>

时间:2012-05-07 08:38:29

标签: c# .net .net-4.0 ienumerable covariance

我想知道,IEnumerable<T>为什么只有out而不是in逆变旗?

public interface IEnumerable<out T>

我可以通过这个例子理解out

IEnumerable<string> strings = new List<string>(){"a","b","c"};
IEnumerable<object> objects = strings;

object大于string,因此编译器担心会做类似的事情:

 objects = objects.First().Select(x=>5);// ( we cant insert int to string...)

很好并且理解。

但如果我想将IEnumerable用作插入呢?

类似的东西:

IEnumerable<object> objects = ...;
IEnumerable<string> strings = objects

所以我也可以插入对象......

但问题是没有IEnumerable<in T> ...

我在这里错过了什么吗?

6 个答案:

答案 0 :(得分:4)

您无法插入IEnumerable。不幸的是,没有“IInsertable”。

没有界面可以告诉您只能将项目添加到集合中。这将是in泛型类型。因为您可以在每个集合接口上获取项目,所以不可能有in个泛型参数。

修改

接口IEnumerable<T>只有getter,因此您无法使用该接口向集合中插入任何元素。因此,out的声明中允许IEnumerable<out T>。它告诉编译器,类型T的值只是朝着一个方向,即“出来”。这使它在一个方向上兼容。您可以将IEnumerable<string>用作IEnumerable<object>,因为您只是从中读取。您可以将字符串作为对象读取。但是你无法写入它,你无法将对象作为字符串传递。它可能是整数或任何其他类型。

答案 1 :(得分:4)

Eric Lippert在介绍C#中的co / contravariance之前有一个series of posts,值得一读。

一个简单的例子就是

public class Animal(){
...
}

public class Giraf(){
   public void LookAboveTheClouds(){}
}

IEnumerable<Animal> animals = new list<Animal>{
                                    new Elephant(),
                                    new Tiger(), 
                                    new TinyMouse()};
IEnumerable<Giraf> girafs = animals;

foreach(var giraf in girafs){
   //elephants,tigers and tiny mice does not suuport this
   giraf.LookAboveTheClouds() 
}

换句话说,编译器无法保证您可以安全地将IEnumerable<Animal>用作IEnumerable<Giraf>。所有的长颈鹿都是动物,但不是所有的动物都是长颈鹿

答案 2 :(得分:2)

我在这里看不到你的问题。使用IEnumerable时无法插入,这不是您正在做的事情。您要做的是分配IEnumerable<string>值为IEnumerable<object>但不起作用。当您为IEnumerable<string>分配类型为IEnumerble<object>的对象时,您无法保证列表中的所有对象都是字符串类型。这就是为什么不能这样做。

Covariant,out关键字,接口确保消费部分始终可以理解输出,即IEnumerable<string>可以被转换为IEnumerable<object>的列表,因为{ {1}}而使用string : object列表的人必须知道如何处理object,而不是相反。

Contravariant,string关键字接口使您可以将对象转换为较少指定的泛型,也就是说,in可以转换为IComparer<object>,因为实现IComparer<string>的类现在必须如何处理IComparer<object>因为它是逆变而不是反过来。

在此处阅读更多内容:http://msdn.microsoft.com/en-us/library/dd799517.aspx

答案 3 :(得分:1)

我认为输入安全性是严格意义的:如果你在列表中怎么办? IEnumerable<object> {"Hello", 2.34d, new MyCoolObject(), enumvalue}

运行时应如何处理这些转换?

如果string可能是一个简单的情况,因为它应该足以调用ToString()的覆盖(如果有的话),但框架设计师不能依赖关于定制案例,应提供通用解决方案。

希望这有帮助。

答案 4 :(得分:1)

正如评论中已经提到的,您不能使用IEnumerable来修改集合。

由于您提到的类型安全问题(将int插入到字符串列表中),编译器将不允许类似下面的代码,因为您可以使用对象修改字符串列表

IEnumerable只是集合上的迭代器。

static void Main(string[] args)
{
    IList<string> strings = new List<string> { "a", "b", "c" };
    IList<object> objects = strings;  ** // This will give an error because compiler does not want you to do the line below **
    objects.Add(5);  // Type safety violated for IList<string> hence not allowed


    // The code below works fine as you cannot modify the strings collection using IEnumerable of object
    //IEnumerable<string> strings = new List<string> { "a", "b", "c" };
    //IEnumerable<object> objects = strings;
}

希望这有帮助。

答案 5 :(得分:0)

您无法将IEnumerable<Parent>分配给IEnumerable<Child>,因为它可能包含 Child的元素。

  

但如果我想将IEnumerable用作插入呢?类似的东西:

IEnumerable<object> objects = ...;
IEnumerable<string> strings = objects

在编译时无法知道 IEnumerable<object>不包含非string。但是,如果您(程序员)知道那里只有string,那么您可以在运行时进行转换:

IEnumerable<string> strings = objects.Cast<string>();

顺便说一下,您可能需要查看标记description