Why does IEnumerable only have the “out” covari

2019-07-10 11:30发布

问题:

I was wondering , why does IEnumerable<T> has ONLY the out and not the in contravariant flag ?

public interface IEnumerable<out T>

I can understand the out by this example :

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

object is bigger than string , so the compiler afraid that well do something like:

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

fine and understood.

but what about if i want to use the IEnumerable as insertion?

something like :

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

so i can also INSERT into objects ...

but the problem is that there is no IEnumerable<in T> ...

am I missing something here?

回答1:

You can't insert into IEnumerable. There is, unfortunately, no "IInsertable".

There is no interface to tell that you can only add items to a collection. This would be an in generic type. Because you can get the items on every collection interface, it is not possible to have in generic arguments.

Edit

The interface IEnumerable<T> has only getters, so you can't insert any elements to the collection using that interface. Because of that, out is allowed in the declaration of IEnumerable<out T>. It tells the compiler, that values of type T are only going in one direction, the are "coming out". This makes it compatible in one direction. You can use IEnumerable<string>as an IEnumerable<object>, because you are only reading from it. You can read a string as an object. But you couldn't write to it, you couldn't pass an object as a string. It might be an integer or any other type.



回答2:

Eric Lippert has a series of posts on this prior to introducing co/contravariance in C# which is worth a read.

a simple example would be

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() 
}

in other words there's no way the compiler can guarantee that you can safely use your IEnumerable<Animal> as an IEnumerable<Giraf>. All girafs are animals but not all animals are girafs



回答3:

I don't see your problem here. When using IEnumerable you can't insert, and that is not what you are doing. What you are trying to do is assigning a IEnumerable<string> a value of IEnumerable<object> and that won't work. When you are assigning your IEnumerable<string> with an object that is of type IEnumerble<object> you can't guarantee that all the objects in the list is of type string. That is why can't do that.

Covariant, the out keyword, interfaces ensures that the output can always be understood by the consuming part, that is, an IEnumerable<string> can be casted to a list of IEnumerable<object> since string : object and someone consuming a list of object must know how to handle a string but not the other way around.

Contravariant, the in keyword, interfaces enables you to cast objects to less specified generics, that is, IComparer<object> can be casted to IComparer<string> since a class implementing IComparer<object> must now how to handle string since it is contravariant but not the other way around.

Read more here: http://msdn.microsoft.com/en-us/library/dd799517.aspx



回答4:

I think it's strictly realted to type safity: what if you in your list
IEnumerable<object> {"Hello", 2.34d, new MyCoolObject(), enumvalue} ?

How runtime should treat those conversions ?

In case of string it could be a simple case, cause it should be enough to call override (if any) of ToString() , but framework designer can not rely on custom cases and should provide generic solutions.

Hope this helps.



回答5:

As already mentioned in the comments, you cannot use IEnumerable to modify a collection.

The compiler will not allow code like the one below due to type safety issues as the one you mentioned (inserting int to an list of string) because you can modify the strings lists here using objects

IEnumerable is simply an iterator over a collection.

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

Hope this helps.



回答6:

You can't assign IEnumerable<Parent> to IEnumerable<Child> because it may contain elements that are not Child.

but what about if i want to use the IEnumerable as insertion? something like :

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

There is no way to know at compile time that the IEnumerable<object> doesn't contain a non-string. However, if you (the programmer) know there are only strings there, you can do the casting at run-time:

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

Incidentally, you might want to take a look at description of the tag covariance.