IList<T> vs IEnumerable<T>: Why is this assignment invalid? - c#

Elephant : Animal....
IList<Elephant> elephants = new List<Elephant>();
IEnumerable<Animal> animalList = elephants;
this works fine but..
IList<Elephant> elephants = new List<Elephant>();
IList<Animal> animalList = elephants;
throws an error. Why is that?

If this
IList<Elephant> elephants = new List<Elephant>();
IList<Animal> animalList = elephants;
were possible you could then do this
animalList.Add(new Animal());
but animalList is really a reference to the same referrent as elephants. And since elephants is an IList<Elephant> you'd be trying to add an instance of Animal to an collection that can only contain instances of Elephant.
The reason that it is possible for IEnumerable<T> is that IEnumerable<T> is covariant in the type parameter T. This is because T only appears in "out" positions in the interface IEnumerable<T>. Since you're only consuming instances of T from the IEnumerable<T>, it is safe to assign instances of IEnumerable<Derived> to variables of type IEnumerable<Base>. Anything that comes out of IEnumerable<Derived> is a Base and this is why something that implements IEnumerable<Derived> can be used as a IEnumerable<Base> safely. This is why it is safe to assign an instance of IEnumerable<Elephant> to a variable of type IEnumerable<Animal>.
But you get in trouble with IList<T> because IList<T> is not covariant in the type parameter T. The issue here is that T appears in "in" positions in the interface IList<T>. So if you could assign instances of IList<Derived> to variables of type IList<Base>, then you could try to stick instances of Base into the IList<Base> which would really be trying to stick instances of Base into an instance of IList<Derived> and that is clearly not safe to do. This is exactly the issue that we just ran into with IList<Elephant> and IList<Animal>.
Note that IList<T> is also not contravariant in the type parameter T. This is because T appears in "out" positions in the interface IList<T>. If you could assign instances of IList<Base> to a variable of type IList<Derived> then you could try to grab an instance of Derived out of the IList<Derived> which would be trying to grab an instance Derived out of an instance of IList<Base> and that is clearly absurd.

This is called covariance and contravariance.
I won't go into details but you can read about it.

Related

Generic delegate cannot have contravariant return type

A few days ago I started reading a C# manual. To this day, I've understood most of the concepts that were introduced pretty clearly except for this one thing:
delegate T TestDelegate<in T>();
This line of code doesn't even compile and I know that it is because the return type cannot be contravariant, I just don't understand WHY it can't be so.
Theodoros's answer is correct. A way I like to think about this question is to ask myself "suppose this were legal; what could go wrong?"
delegate T D<in T>(); // Suppose this were legal.
class Animal {}
class Tiger : Animal {}
class Giraffe : Animal {} // Plainly all these are legal.
...
D<Animal> da = () => new Tiger(); // A tiger is an animal, so this must be legal.
D<Giraffe> dg = da; // This is legal because T is declared contravariant in D.
Giraffe g = dg(); // This is legal, because dg returns a giraffe.
// Except that it actually returns a tiger, and now we have a tiger in
// a variable of type giraffe.
Every line in that little program fragment is obviously correct except for the first one. The program is not type safe and therefore must be illegal. Therefore we must conclude that the first line is the one which must be illegal, and it is.
A generic type parameter is contravariant if the declaring type becomes more general as the type argument becomes more specific, and vice versa. In other words, the delegate's generality runs in the opposite direction (contra) to that of the type parameter.
From the .NET class hierarchy, we know that a string is a special kind of object.
But TestDelegate<T> is a type for functions that promise to return a T. Are functions that return an object a special case of functions that promise to return a string? Or are functions that return a string a special case of functions that promise to return an object?
Clearly the latter is the case, which is why T can only be covariant, meaning that its generality goes the same direction as the declaring type. (Or invariant, but we don't care about that here.)

Cast interface list to implementation list or vice-versa

I am just not getting how to convert or cast List<ISomeImplementation> to List<SomeImplementation>.
// lstOfImplemantation is of type `List<SomeImplementation>`
List<ISomeImplementation> lstOfInterfaces = lstOfImplemantation; // how to convert it.
any help is appreciated.
You can use LINQ in one way:
List<ISomeImplementation> lstOfInterfaces = lstOfImplemantation.Cast<IISomeImplementation>().ToList();
Casting interface to implementation is not good pratice. What if there some other implementations of that interface? You cannot assume that all implemtations are the of type SomeImplementation. If you are sure, use List<SomeImplementation>.
Just because A derives from B, doesn't necessarily mean X<A> derives from X<B>.
In fact, List<T> is invariant in its generic type paramater T. This means List<string> isn't a subtype nor a supertype of List<object>, even though string derives from object.
Imagine if your list of interfaces contained instances of type SomeOtherImplementation. How would the cast work?
Instead, you should create a new list and cast each item in your list of interfaces to the concrete type.
List<SomeImplementation> lstOfImplementation =
lstOfInterfaces.Cast<SomeImplementation>()
.ToList();
If you're not sure whether all instances are of type SomeImplementation, you can filter out those who aren't, using OfType<T>.
List<SomeImplementation> lstOfImplementation =
lstOfInterfaces.OfType<SomeImplementation>()
.ToList();
Read also:
Covariance and Contravariance
Covariance and Contravariance FAQ

Casting list of objects to List vs IList

Just came across this:
Func<List<object>> foo = () => new List<object>();
List<string> s = (List<string>)foo();
IList<string> s1 = (IList<string>)foo();
Compiler complains about casting to List (makes sense), but says nothing about IList. Makes me wonder why is that?
The compiler knows that a List<X> cannot be a List<Y>.
It therefore gives a compiler error.
However, the second cast could succeed if the List<X> is actually some derived class that also implements IList<Y>.
You will only get a compile-time error from a cast if neither type is an interface, or if one type is an unrelated interface and the other type is sealed (or a struct).
To quote the spec (ยง6.4.2)
The explicit reference conversions are:
From object and dynamic to any other reference-type.
From any class-type S to any class-type T, provided S is a base class of T.
From any class-type S to any interface-type T, provided S is not sealed and provided S does not implement T.
From any interface-type S to any class-type T, provided T is not sealed or provided T implements S.
From any interface-type S to any interface-type T, provided S is not derived from T.
[snip]
(emphasis added)
The provided... clauses exclude conversions that are actually implicit.
An object that is known to be a List<object> might implement IList<string> as well as IList<object>, so it's possible that the cast can succeed. It can't in this case because we know that the statement is simply new List<object>(), but the compiler doesn't consider that. You might've extended List<T> and implemented the other, e.g.
// not recommended, but second cast works
public class MyWeirdList : List<object>, IList<string>
An object that is known to be a List<object> cannot possibly also be a List<string>, because you can only inherit from a single type.
public class MyWeirdList : List<object>, List<string> // compiler error
If List<T> were sealed, both casts would be invalid, because then the compiler would know for sure that the class couldn't implement IList<string>. You can try this by using this class instead:
public sealed class SealedList<T> : List<T> { }
The first line fails at compile time, the second gives an "Unable to cast object of type 'System.Collections.Generic.List1[System.Object]' to type 'System.Collections.Generic.IList1[System.String]'." exception during runtime.
If you look at this question (Cast IList<string> to IList<object> fails at runtime), the answer clarifies why this compile works and also provides an example for a class that could satisfy the conditions provided.

Object assignment in C#

This is something I encountered while using the C# IList collections
IList<MyClass> foo = new List<MyClass>();
var bar = new List<MyClass>();
foo.AddRange() // doesn't compile
bar.AddRange() // compile
As far as I know, in C# (on the contrary of C++) when we create an object with this syntax, the object type get the right side (assignment) and not the left one (declaration).
Do I miss something here !
EDIT
I still don't get it, even after your answers, foo and bar have the same type !
There's nothing so subtle going on here:
foo has the type IList<MyClass> and IList<T> doesn't have an AddRange method
bar has the type List<MyClass> and List<T> does have an AddRange method.
That's all. It would be just the same in C++.
Update: In the edited-in addition, you're calling GetType(), which gets the run-time type of the object - at compile time the compiler is looking at the static type of the foo and bar variables.
Thinking about Object myObj = "MyString" might make things clearer, because there is a more obvious difference between an 'Object' and a 'String', even though they have the same inheritance relationship as IList and List
The problem is that you are using the interface and IList does not have AddRange but List does have AddRange
If you change it to
List<MyClass> foo = new List<MyClass>();
it will work. as it has this method.
Here is what IList has
You can also do
IEnumerable<MyClass> foo = new List<MyClass>();
And you will find that this constrains it even more
EDIT FOR YOUR EDIT:
They will both be the same type becuase both are still Lists. The difference comes in with the variable that you are using. By using the interface, you are limiting the operations to those that the interface supports. As List implements IList, IList has a subset of the operations that List has.
The underlying object is still a List.
The underlying type of foo is List, but its static type, which the compiler uses to ensure correctness, is IList<T>. Anything that you invoke on foo must be declared as part of the IList<T> type.
Try:
List<MyClass> foo = new List<MyClass>(); // CHANGED TO List<> rather than IList<>
var bar = new List<MyClass>();
foo.AddRange() // doesn't compile
bar.AddRange() // compile
Also the IList declares a behavior and not a concrete type. Please see here
http://msdn.microsoft.com/en-us/library/system.collections.ilist%28v=vs.110%29.aspx
In the a List you can add items and IList type is an already constructed type, which implements this interface.
I have not understood your question, but in this situation C# and C++ act the same way. In your example that to provide that the code would be compiled you could write
IList<MyClass> foo = new List<MyClass>();
var bar = new List<MyClass>();
( (List<MyClass> )foo ).AddRange();
bar.AddRange() // compile
In fact it has the same sense as dynamic_cast in C++.
As the all said IList does not support AddRange and it does not need to support it.
You can:
1 ) ((List<MyClass>)foo).AddRange(anotherList);
2 ) You can also use extension method to AddRange
3 ) There are another workarounds for this problem e.g. Concat etc.
I think also, the interface shall not support every function on the class at some point you need abstraction and this shall be based on interface segregation principle.

Difference between covariance and upcasting

What is the difference between covariance and upcasting, or, more specifically, why are they given different names?
I've seen the following example referred to as 'upcasting':
string s = "hello";
object o = s; //upcast to 'string' to 'object'
Whereas, the following I have seen called 'covariance':
string[] s = new string[100];
object[] o = s;
IEnumerable<string> ies = new List<string>();
IEnumerable<object> ieo = ies;
Now, to my untrained eye, covariance seems to be the same as upcasting, except that it refers the casting of collections. (And of a similar statement can be made regarding contravariance and downcasting).
Is it really that simple?
Now, to my untrained eye, covariance seems to be the same as upcasting, except that it refers the casting of collections. (And of a similar statement can be made regarding contravariance and downcasting).
Is it really that simple?
Covariance isn't about upcasting, although I can see why you think it's related.
Covariance is about the following very simple idea. Let's say you have a variable derivedSequence of type IEnumerable<Derived>. Let's say you have a variable baseSequence of type IEnumerable<Base>. Here, Derived derives from Base. Then, with covariance, the following is a legal assignment, and an implicit reference conversion occurs:
baseSequence = derivedSequence;
Note that this is not upcasting. It is not the case that IEnumerable<Derived> derives from IEnumerable<Base>. Rather, it is covariance that allows you to assign the value of the variable derivedSequence to the variable baseSequence. The idea is that variables of type Base can be assigned from objects of type Derived, and since IEnumerable<T> is covariant in its parameter, objects of type IEnumerable<Derived> can be assigned to variables of type IEnumerable<Base>.
Of course, I haven't yet really explained what covariance is. In general, covariance is about the following simple idea. Let's say you have a mapping F from types to types (I'll denote this mapping by F<T>; given a type T its image under the mapping F is F<T>.) Let's say that this mapping has the following very special property:
if X is assignment compatible with Y, then F<X> is assignment compatible with F<Y> as well.
In this case, we say that F is covariant in its parameter T. (Here, to say that "A is assignment compatible with B" where A and B are reference types means that instances of B can be stored in variables of type A.)
In our case, IEnumerable<T> in C# 4.0, an implicit reference conversion from instances of IEnumerable<Derived> to IEnumerable<Base> if Derived is derived from Base. The direction of assignment compatibility is preserved, and this is why we say that IEnumerable<T> is covariant in its type parameter.
Casting refers to changing the static type of objects and expressions.
Variance refers to the interchangeability or equivalence of types in certain situations (such as parameters, generics, and return types).
IEnumerable<string> is not derived from IEnumerable<object>, so the cast between them is not upcasting. IEnumerable is covariant in its type parameter and string is derived from object, so the cast is allowed.
The reason they are different concepts is that, unlike upcasting, covariance is not always allowed. It would have been easy for the designers of the type-system to make IList<Cat> be considered as "derived" from IList<Animal>, but then we run into problems:
IList<Cat> cats = new List<Cat>();
IList<Animal> animals = cats;
animals.Add(new Dog()); //Uh oh!
If this were allowed, now our cats list would contain a Dog!
In contrast, the IEnumerable<T> interface has no way of adding elements, so this is perfectly valid (in C# 4.0):
IList<Cat> cats = new List<Cat>();
IEnumerable<Animal> animals = cats;
//There's no way to add things to an IEnumerable<Animal>, so here we are ok
Thie blog post below has a good explanation of it:
http://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx
From what I can gather covariance removes the need for explicit downcasting subsequent to a previous upcast. Typically if you upcast an object you can only access the base type methods and attributes, with covariance it seems you can imply the downcast by replacing lesser derived types with more derived types in the more derived class declaration.

Categories

Resources