C# variance problem: Assigning List as List<Base>

前端 未结 5 816
醉酒成梦
醉酒成梦 2020-11-21 06:34

Look at the following example (partially taken from MSDN Blog):

class Animal { }
class Giraffe : Animal { }

static void Main(string[] args)
{
    // Array a         


        
相关标签:
5条回答
  • 2020-11-21 07:03

    Well this certainly won't be supported in C# 4. There's a fundamental problem:

    List<Giraffe> giraffes = new List<Giraffe>();
    giraffes.Add(new Giraffe());
    List<Animal> animals = giraffes;
    animals.Add(new Lion()); // Aargh!
    

    Keep giraffes safe: just say no to unsafe variance.

    The array version works because arrays do support reference type variance, with execution time checking. The point of generics is to provide compile-time type safety.

    In C# 4 there will be support for safe generic variance, but only for interfaces and delegates. So you'll be able to do:

    Func<string> stringFactory = () => "always return this string";
    Func<object> objectFactory = stringFactory; // Safe, allowed in C# 4
    

    Func<out T> is covariant in T because T is only used in an output position. Compare that with Action<in T> which is contravariant in T because T is only used in an input position there, making this safe:

    Action<object> objectAction = x => Console.WriteLine(x.GetHashCode());
    Action<string> stringAction = objectAction; // Safe, allowed in C# 4
    

    IEnumerable<out T> is covariant as well, making this correct in C# 4, as pointed out by others:

    IEnumerable<Animal> animals = new List<Giraffe>();
    // Can't add a Lion to animals, as `IEnumerable<out T>` is a read-only interface.
    

    In terms of working around this in your situation in C# 2, do you need to maintain one list, or would you be happy creating a new list? If that's acceptable, List<T>.ConvertAll is your friend.

    0 讨论(0)
  • 2020-11-21 07:23

    It will work in C#4 for IEnumerable<T>, so you can do:

    IEnumerable<Animal> animals = new List<Giraffe>();
    

    However List<T> is not a covarient projection, so you cannot assign lists as you have done above since you could do this:

    List<Animal> animals = new List<Giraffe>();
    animals.Add(new Monkey());
    

    Which is clearly not valid.

    0 讨论(0)
  • 2020-11-21 07:25

    Covariance/contravariance can't be supported on mutable collections as others have mentioned because it's impossible to guarantee type safety both ways at compile time; however, it is possible to do a quick one-way conversion in C# 3.5, if that is what you're looking for:

    List<Giraffe> giraffes = new List<Giraffe>();
    List<Animal> animals = giraffes.Cast<Animal>().ToList();
    

    Of course it's not the same thing, it's not actually covariance - you're actually creating another list, but it is a "workaround" so to speak.

    In .NET 2.0, you can take advantage of array covariance to simplify the code:

    List<Giraffe> giraffes = new List<Giraffe>();
    List<Animal> animals = new List<Animal>(giraffes.ToArray());
    

    But be aware that you're actually creating two new collections here.

    0 讨论(0)
  • 2020-11-21 07:27

    In terms of List<T>, I'm afraid you're out of luck. However, .NET 4.0/C# 4.0 adds support for covariant/contravariant interfaces. Specifically, IEnumerable<T> is now defined as IEnumerable<out T>, which means that the type parameter is now covariant.

    This means you can do something like this in C# 4.0...

    // implicit casting
    IEnumerable<Animal> animalsList = new List<Giraffe>();
    
    // explicit casting
    IEnumerable<Animal> animalsList2 = (IEnumerable<Animal>) new List<Giraffe>();
    

    Note: Array types have also been covariant (at least since .NET 1.1).

    I think it's a shame that variance support wasn't added for IList<T> and other similar generic interfaces (or generic classes even), but oh well, at least we have something.

    0 讨论(0)
  • 2020-11-21 07:27

    GenericClass<DerivedClass> and GenericClass<BaseClass> and two distinct closed constructed generic types of GenericClass<> open generic type and they not inherit one from the other.

    So you can't cast GenericClass<B> to GenericClass<A> even if B inherits from A.

    It is like you ask to cast this:

    class A2 : A1;
    class B2 : B1;
    
    var a2 = new A2();
    var b2 = new B2();
    var x = (A1)b2;
    

    GenericClass<DerivedClass> and GenericClass<BaseClass> are as distinct as A1 and B2.

    But they are all object.

    And since there is no diamond operator in C# yet, you can't use true polymorphism on open generic type underlying to closed constructed types like this:

    var x = (GenericClass<>)b;
    

    You can't create list like this:

    List<> list;
    

    You can't do polymorphism on such list... and it is a lack in genericity here.

    For example, in C# you can't create a List<Washer<>> instance to have some Washer<Cat> and some Washer<Dog> to operate Wash() on them... and to do that you need an ugly interface pattern...

    Generics -Open and closed constructed Types

    About the lack of true generic polymorphism and the missing diamond operator in C#

    0 讨论(0)
提交回复
热议问题