可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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 string
s 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.