LINQ Farm: Covariance and Contravariance in C# 4.0


This post covers the upcoming C# 4.0 support for covariance and contravariance when working with delegates and interfaces. Eric Lippert’s series of posts on this subject are definitely the definitive reference at this time. I’m writing this overview of the subject simply as an appendix to his explanation, and as quick reference for folks who want to get up to speed on this technology. Please remember that this post covers pre-beta technology as defined in the October 2008 CTP of Visual Studio 2010.

The support for covariance and contravariance in the next version of the C# language will ensure that delegates and interfaces will behave as expected when you are working with generic types. In Visual Studio 2008, there are rare occasions when developers might expect a delegate or interface to behave one way, only to find that it does not conform to their expectations. In Visual Studio 2010 delegates will behave as expected. Other C# types support have always automatically supported covariance and contravariance. C# 4.0 will simply ensure that generic delegates and interfaces follow suit.

Consider this simple class hierarchy:

class Animal{ }
class Cat: Animal{ }

Here we have a class called Animal, and a simple descendant of it called Cat. Suppose you declare a delegate declaration that defines a method that returns an arbitrary type:

delegate T Func1<T>();

An instance of this delegate could be defined to return a Cat:
Func1<Cat> cat = () => new Cat();

The delegate declaration shown here defines a delegate that returns a type T. The second line of code initializes T to be of type Cat and assigns this delegate to a simple lambda that creates an instance of a cat and returns it. For those of you not yet comfortable with lambda syntax, I’ll add that the lambda shown here is simply a shorthand way of writing a method that looks like this:

public Cat MyFunc()
{
    return new Cat();
}

So one could also have written the cat delegate like this:

Func1<Cat> cat = MyFunc;

Given either of these declarations, our intuition tells us that we could assign a cat to a delegate that returns an Animal:

Func1<Animal> animal = cat;

This code looks like it should succeed because a Cat is type of Animal, and one should always be able to assign a smaller type of object, such as a Cat, to a larger type of object, such as a Animal.  We think of the type Animal as being large, since a creature such as a Cat, Dog, Whale or Bird would be a type of Animal, and hence compatible with it. In other words, a big type like an Animal is assignment compatible with lots of smaller types, such as a Cat, Dog or Whale. We think of the type Cat as being smaller than an Animal, since only other cats can be assigned to it. It is not assignment compatible with as wide a range of types as an Animal, and hence we think of it as being smaller.

In fact, the assignment shown above does not work in Visual Studio 2008. The new feature being added in Visual Studio 2010 ensures that this assignment will work if you make one minor change to the declaration for your delegate. In particular, you need to use the keyword out in your type parameter:

delegate T Func1<out T>();

Now the assignment will succeed, and the code will run as expected, as shown in Listing 1. This type of assignment is called covariance.

Listing 1: Examples of covariance and contravariance.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SimpleVariance
{
    class Animal { }
    class Cat: Animal { }

    class Program
    {
        // To understand what the new CoVariance and ContraVariance code does for you
        // Try deleting or adding the words out and in from the following 2 lines of code:
        delegate T Func1<out T>();
        delegate void Action1<in T>(T a);

        static void Main(string[] args)
        {
           // Covariance
            Func1<Cat> cat = () => new Cat();
            Func1<Animal> animal = cat;

            // Contravariance
            Action1<Animal> act1 = (ani) => { Console.WriteLine(ani); };
            Action1<Cat> cat1 = act1;
        }        
    }
}

Looking at Listing 1, you will notices that there is a second example included in it. This second example illustrates contravariance. Most developers, including myself, find contravariance more difficult to understand than covariance. It is, however, a similar concept.

The second example starts out by including a delegate declaration. This time the declaration defines a method that takes a parameter, rather than returning a value:

delegate void Action1<in T>(T a);

Notice also that we use the keyword in, rather than the keyword out. That is because we are passing a parameter in, rather than returning a result “back out” of a function.

Next we define a delegate that takes an animal as a parameter:

Action1<Animal> act1 = (ani) => { Console.WriteLine(ani); };

In the contravariant example shown in Listing 1, the assignment of act1 to the delegate cat1 would fail if we did not include the keyword in when declaring the Action1 delegate type. Here is the assignment:

Action1<Cat> cat1 = act1;

This would fail in C# 3.0 no matter what you did. It will work in C# 4.0 so long as you use the keyword in when declaring the delegate type Action1. If you omitted the keyword, then it would fail. For instance, the following declaration of Action1 would cause the assigned to fail because the keyword in is omitted:

delegate void Action1<T>(T a);

Contrast this declaration with the one in Listing 1.

In the contravariant example we use a lambda to define a method that is equivalent to a standard method that looks like this:

public static void Contra(Animal ani)

{

      Console.WriteLine(ani);

}

I’m using lambdas in Listing 1 simply to keep the code short, not because lambdas are connected to covariance and contravariance. This code would still illustrate covariance and contravariance whether or not I used lambdas. What is important here is not the presence of lambdas, but the presence of a generic delegate.

Finally, lets take one moment to make sure you understand why the assignment of act1 to cat1 works. The peculiar thing about contravariance is that it appears that we are assigning a larger type to a smaller type. In other words, it appears that we are trying to fit a big thing like an Animal, into a small type like a Cat. This is not what we are doing. The point here is that act1 will work with any animal, and a Cat is animal, therefore you can safely make the assignment. In other words, cat1 can only be passed Cats, and all cats are animals, therefore anything you can assign to cat1 could also be safely passed to act1. This is why the assignment should work, and does in fact work if you use the keyword in.

As I say, contravariance is a bit confusing. If you find the subject a bit much, then you are joining a happy throng of other developers who struggle with the subject. My suggestion is just to relax, think about the example I’ve shown here for a bit, and you will probably have an “ah-ha” moment either in the next few moments, or sometime in the hopefully very lengthy remainder of your life.

Almost everyone who has written or talked about this subject, from Eric Lippert to Anders himself, points out that this technology is not so much an innovation as a means of bringing C# in line with developers expectations. Adding support for covariance and contravariance to generic interfaces and delegates simply ensures that the language behaves as many would intuitively expect it to behave. All you have to do is remember to add the the keywords in or out when you are making an assignment that involves delegates or interfaces, and the language isn’t behaving as you would expect it to behave.

I apologize for focusing only on delegates in this post. Hopefully I’ll find the time to come back and illustrate the same subject using interfaces. I should add that all the code shown here is written against a very shaky pre-beta version of the C# 4.0, and it is always possible, though that not necessarily likely, that this technology will change before it ships.

kick it on DotNetKicks.com

Comments (37)

  1. You’ve been kicked (a good thing) – Trackback from DotNetKicks.com

  2. Josh Berke says:

    Couple of questions for you:

    Can a type param be both in and out so would the following be allowed:

    public interface ICopier<in out T>

    {

    T Copy(t item);

    }

    Also can you explain why the need for the extra keywords? I can’t imagine why but I am sure there is a reason.

    thanks

    Josh

  3. iCe says:

    I’m with Josh, please explain us why we need those in-out keywords. I can’t figure out why is needed to do something that is what you expect, specially when talking about covariance

  4. Daedius says:

    I think I understand the background.  This is a feature created to support F# functional programming types.  Immutable functions without side effects is a HUGE thing in functional programming.  The contravariant delegates are a way of representing these functions in C#.

    Basically, delegates with "in" parameters are delegates that can produce no side effects. "out" delegates are the more common garden variety delegates we all know and love.

  5. iCe says:

    Daedius, in this example the "out" keyword is not a "common delegate". Indeed, it is making something that actually cannot be done "covariance".

    delegate T Func1<out T>();

           delegate void Action1<in T>(T a);

           static void Main(string[] args)

           {

              // Covariance

               Func1<Cat> cat = () => new Cat();

               Func1<Animal> animal = cat;

               // Contravariance

               Action1<Animal> act1 = (ani) => { Console.WriteLine(ani); };

               Action1<Cat> cat1 = act1;

           }        

    What I´m really want to know, is why the compiler cannot do this automatically, simply by allowing you do covariance and contravariance without any keyword at all. I think if it was possible actually in some parts of the language why not in generics and delegates? As Charlie has said "In Visual Studio 2010 delegates will behave as expected", so if it’s something we expect, why we are forced to use a special keyword to get expected behavior?

  6. Halo_Four says:

    It is a limitation in the name of type safety.  You are declaring that the interface or delegate can accept base or derived classes of the generic parameter by explicitly defining that said interface or delegate will either only allow T as input, or only allow T as output.  The interface or delegate cannot be both.

    If you are using covariance it means that the interface or delegate can output a value that is a class of the specified type parameter or derived from that class.  If you pass a Func<Cat> to a method that expects a Func<Animal> it works fine since it will return a Cat, which is derived from Animal.  That method can then treat the Cat as if it were an Animal without any ill effects.

    If you are using contravariance it means that the interface or delegate will receive input of a value that is a class of the specified type parameter or derived from that class.  If you pass an Action<Animal> to a method that expects an Action<Cat> it works fine as it will pass a Cat which is derived from Animal.  The body of the called delegate can treat the Cat as if it were an Animal without any ill effects.

    These roles are pretty specific and cannot be reversed.  A method that takes a Func<Cat> cannot accept a Func<Animal> because the Animal that it returns might not be a valid Cat and it would fail.  Similarly a method that takes an Action<Cat> cannot accept an Action<Animal> for the same reason.  The new keywords really only apply to the implementers of fairly fundamental structures, such as IEnumerable<out T> or Action<in T>.  Their benefit comes offering the flexibility in the consumption of types that consume those structures.  You might never use those keywords, but because of them you can now do this:

    // Illegal in C# 3.0

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

  7. Bruce Pierson says:

    "IEnumerable<Animal> animals = new List<Cat>();"

    This is HUGE. I can’t believe how many special interfaces I have to use to get true "programming to an interface" right now just because of this limitation.

    For example, right now I have to do this:

    Domain object, with CRUD or Mapper ops (much easier to code to a concrete or abstract class collection than to an interface that has no CRUD methods):

    public IList<RuleState> ChildStates

    {

       get { return m_ChildStates; }

    }

    then in the interface for my runtime configurator (on the same class), I have to do this:

    IList<IRuntimeRuleState> IRuntimeConfig.GetRuleStates()

    {

      List<IRuntimeRuleState> list = new List<IRuntimeRuleState>();

       foreach(IRuntimeRuleState rs in m_ChildStates)

           list.Add(rs);

       return list;

    }

    In c# 4, I could just do:

    IList<IRuntimeRuleState> IRuntimeConfig.GetRuleStates()

    {

      return m_ChildStates;

    }

    Much easier to code, not to mention far more efficient.

    Can’t wait.

  8. Bruce Pierson says:

    Your comment about this "acting as the programmer expects" is exactly right. I was very excited about generics, but my excitement was tempered when I found out I couldn’t do:

    private List<SomeObject> m_Field;

    public IList<ISomeObject> Prop

    {

       get { return m_Field; }

    }

    As I posted previously, this covariance fix will alleviate this problem, and make us all better programmers by giving us the tools to be better programmers.

  9. int19h says:

    Unfortunately, it won’t alleviate your problem specifically, because IList<T> cannot be covariant. The reason is that it uses T both in arguments of methods – such as "void IList<T>.Add(T)"; and in return values – such as "T IList<T>.this[int]". This means that you won’t be able to write this in C# 4.0:

    interface IFoo { … }

    class Foo : IFoo { … }

    IList<IFoo> list = new List<Foo>();

    In fact, at a quick glance, there are only a few basic types in the FCL that would be covered by new variance declarations: Predicate<T>, Comparer<T>, Action<…> and Func<…>, IEnumerable, IEquatable, IComparable. ICollection<T> and anything further down the inheritance chain will have to remain invariant.

    As a result, I doubt that we’ll see "in" and "out" in application code in class declarations often (if at all). On the other hand, it will probably become good style to always use them when declaring delegate types.

  10. Luis Abreu says:

    I’m with int19h on this one. Since you cannot have both in and out applied to the parameter, it seems like you won’t be able to use covariance/contravariance with the most interesting collections…

    btw, should I assume that we won’t be getting Design By Contract on C# 4.0? Why isn’t this part of tha language?It’s one of those things that should have been there for a long time!

  11. LA.NET [EN] says:

    One of the things that we’ll have in C# 4.0 is covariance/contravariace working on generics…I mean, sort

  12. OOP Sceptic says:

    Really guys, this is so far away from what programming really means, ie working with real people to find out what they want and then implementing it that it’s a total waste of time.

    Has anyone here actually done a programming for a living (and I don’t count writing programming books or similar wanky stuff as programming)

  13. ASPInsiders says:

    One of the things that we’ll have in C# 4.0 is covariance/contravariace working on generics…I mean, sort

  14. iCe says:

    Thanks Halo_Four, you have cleared a lot of things.

    So the problem here is with classes that output and input the same generic parameter, so that is why it cannot be done without the "in" and "out" keywords. Also note that as Bruce Pierson and int19h commented, unfortunately this will not cover all the expected cases, like those when the parameter is used both in and out.

    OOP Sceptic I´m in a real world project right know and those discussions allow me to understand better the tool that I´m using every day. And my better understanding of the tool means better products for the client, and better support.

  15. Thanks, int19h, for the clarification, even though it’s depressing…

    @OOP Sceptic

    I currently have many customers using my software to actually run their manufacturing businesses (boats, trailers, clothing, and more), and it suffers from being difficult to modify to suit their needs because of the lack of flexible design patterns, and old-style procedural programming. I’m currently re-writing it using expert design pattern guidance, and I cannot believe the difference it makes to my sanity when I take the time to understand and apply these principles.

  16. This is indeed very promising and will be helpful in many scenarios, but I’m dissapointed to see that it doesn’t solve the problems with ICollection and IList.

    Would it not be possible to define a new set of collection interfaces without this problem?

    Or alternatively, add a mechanism that allows specifying the variance at the method level instead of at type level, so that when a generic type parameter is used for both in and out the specific use (for a method) can be explicitly specified.

    It may be necessary or desired to also specify the variance on consumption, similar to how ref/out must be specified both at declaration and on invocation.

    Mind you, I don’t usually think about these things and Eric clearly lives in another dimension.. 🙂

    That said, surely there must be some way to allow people to write "IList<IFoo> list = new List<Foo>();" as this is such a common pattern. I’ll take compiler magic like for "int? i = null" over any "will not compile" error.

  17. Richard says:

    You can’t allow "IList<IFoo> list = new List<Foo>();", otherwise you could end up with:

    class Foo : IFoo {}

    class Bar : IFoo {}

    IList<IFoo> list = new List<Foo>();

    list.Add(new Bar());

    The only option would be to split the interface declaration into four parts – non-generic (Clear, Count, IsReadOnly, RemoveAt), covariant (GetEnumerator, indexer), contravariant (Add, Contains, IndexOf, Insert, Remove) and invariant (CopyTo).

    Note that CopyTo would have to be invariant because it accepts an array of type T. You can only pass an array of U to a method which expects an array of type T if U is derived from or equal to T, but you can only put an object of type T in an array of type U if T is derived from or equal to U. Therefore, the only type U which satisfies both conditions is the type T, and the method must be invariant.

    I think that this also explains why the compiler can’t automatically infer the "variance-ness" of a type parameter.

    Example:

    static void Fill(IFoo[] values)

    {

       values[0] = new Foo();

       values[1] = new Bar();

    }

    Fill(new object[2]); // Compiler error

    Fill(new Foo[2]); // ArrayTypeMismatchException

    Fill(new Bar[2]); // ArrayTypeMismatchException

    Fill(new IFoo[2]); // Works

    And if you can follow that gibberish, you’ve probably had too much coffee! 😉

  18. Richard says:

    Sorry – I obviously meant to say that the indexer get would be covariant, while the indexer set would be contravariant.

  19. Thomas Krause says:

    Nice to finally see this in the language, but the choice of keywords is just horrible:

    1.) we already have the out keyword for parameters, but with completly different semantics. And generic type "parameters" are similar enough to function parameters to confuse many people, especially if they are new to the language.

    2.) without looking I cannot tell which one of the keywords is used for contravariance and which is used for covariance. "In" and "out" is not vocabulary I’d normally use when talking about type hierachy relations.

    3.) there are far better alternatives. Why not simply use "super" and "sub" instead. It is far easier to remember that "sub T" means you can use subtypes of T and "super T" means you can use supertypes of T.

  20. int19h says:

    Actually, "in" and "out" are fairly obvious because they clearly outline the restrictions on the usage of a type parameter. An "in" type parameter can only be used for values that are inputs of methods – that is, non-out/ref arguments. An "out" type parameter can only be used for values that are outputs of methods – return values, and out-arguments

    > Would it not be possible to define a new set of collection interfaces without this problem?

    It is possible if you basically split each collection interface into three parts: covariant, contravariant, and both. E.g. for a list:

    interface IListVariant

    {

     int Count { get; }

     bool IsReadOnly { get; }

     void Clear();

     void RemoveAt(int);

    }

    interface IListCovariant<out T>

    {

     T Item[int] { get; set; }

     IEnumerator<T> GetEnumerator();

    }

    interface IListContravariant<in T>

    {

     void Add(T);

     bool Contains(T);

     void CopyTo(T[], int);

     int IndexOf(T);

     void Insert(int, T);

     bool Remove(T);

    }

    interface IList<T> : IListVariant<T>, IListCovariant<T>, IListContravariant<T>

    {

    }

    And so on for all other collection types (and all other interfaces that could also be so split). So far I haven’t seen any indication that this is going to happen in .NET 4.0 (at least it’s not mentioned on the "what’s new in 4.0" poster), and, looking at the code above, I think it is understandable 🙂

    > Or alternatively, add a mechanism that allows specifying the variance at the method level instead of at type level, so that when a generic type parameter is used for both in and out the specific use (for a method) can be explicitly specified.

    I will repeat what I said elsewhere, and just say that the proper way to enable full variance is to do what Java guys did, and use variance markers at use-site, not at declaration-site, same as Java does it. For example, here are two methods that use IList<T> differently:

    // Method can take IList<T> where T is string or any base class of string

    void AddString(IList<in string> list, string s)

    {

     // cannot use any methods of list that return values of type T here, only those who take arguments of type T

     list.Add(s);

     //list[0]; // illegal here!

    }

    // Method can take IList<T> where T is object or any class derived from object

    object GetFirst(IList<out object> list, int i)

    {

     // cannot use any methods of list that take arguments of type T here, only those that return values of type T

     return list[i];

     //list.Add(s); // illegal here!

    }

  21. OOP Sceptic says:

    @ iCe and Bruce Pierson

    Thanks for responding. I too have many hundreds of people running their finance businesses on my software.

    I appreciate that advances in any particular technology are going to improve that technology, BUT it seems to me that all these somewhat esoteric terminologies are just solutions waiting for problems.

    Maybe this new stuff will help you, but it’s a little late in the day for magic solutions – surely these wacko extension merely highlight the flaws in OOP any way!

  22. Richard says:

    @ int19h:

    CopyTo can’t be contravariant, as I tried to explain above. Passing an array is equivalent to passing a ref parameter. Although the method shouldn’t read from the array, there’s nothing in the declaration to prevent it.

    Although it would be nice if "out" parameters could be used in contravariant interfaces, I don’t think the CLR would support it. As I understand it, the only difference between "out" and "ref" parameters is the C# compiler’s definite assignment checks.

  23. >As I say, contravariance is a bit confusing.

    maybe the names are weird but usage is just polymorphism (some interface is expected).

    I can already see this is going get missused.  instead of using a factory pattern for Covariance

    and aggregation to implement Contravariance. (pass you concrete object implementing an interface into another object that will make it work)

    i wish there was some real world usage in these examples..

  24. Welcome to the 47th Community Convergence. We had a very successful trip to PDC this year. In this post

  25. int19h says:

    > instead of using a factory pattern for Covariance and aggregation to implement Contravariance. (pass you concrete object implementing an interface into another object that will make it work)

    Since covariance and contravariance in C# 4.0 will work only on interfaces (and delegates, which are semantically really just one-method interfaces), I don’t see your point.

    In fact, I don’t understand it at all. How would aggregation help deal with the present problem that IEnumerable<Derived> cannot be treated as (i.e. cast to) IEnumerable<Base>, even though it is clearly typesafe and meaningful to do so?

  26. Josh Berke says:

    @OOP: I think learning about aditions to the language is a great way for us to learn new ways to applys technology solutions through code to our business problems. While I was very skeptical of Linq at first, I have come to enjoy Linq to Objects, as a quick way to filter and sort my collections (Especially when binding to grids).

    @Everyone Else: I still don’t get the need for the new key words, and I’m afraid unless I sat down with an expert who could pound it into my head with a base ball bat I won’t get it. Since I’m the sole developer at my company, guess I’ll have to finally go and attend a community event.

    Thanks

  27. C# 4.0 Dynamic Lookup I really like the way the C# team tackled bring dynamic programming to the language

  28. > IEnumerable<Derived> cannot be treated as (i.e. cast to) IEnumerable<Base>, even though it is clearly typesafe and meaningful to do so?

    just so all can see what you mean, here is the test:

       class Test

       {

           public Test()

           {

               List<Base> items = new List<Base>(this.GetItems());

           }

           public IEnumerable<Derived> GetItems()

           {

               yield return new Derived();

           }

       }

       public class Base

       {

       }

       public class Derived : Base

       {

       }

    it fails to compile.

    I agree semantically it ‘could’ compile.

    On purpose during the Symantic Analysis stage the compiler won’t let it compile.

    Why?

    You should be returning:

           public IEnumerable<Base> GetItems()

           {

               yield return new Derived();

           }

    so you never couple higher layers with concrete types.

    not IEnumerable<Derived> which is not meaningful since the Test class only needs the Base interface.

    again i wish there was some real world examples on why this is actually needed.

  29. Running behind on everything again, picked up a nasty stomach bug that laid me out for a few days (not

  30. Guy kolbis says:

    There are a few new features coming out in C# 4.0. I gathered some posts that will help you to &quot;get

  31. tytusse says:

    As someone pointed much earlier (so I am not 1st) – the keywords are necessary – you cant now – at runtime – what is the actual generic type of generic collection, and furthermore – using "generic" cast you will loose type information which is necessary for compile time checks and leads to stupid runtime fails, of which I dreamed to forget when I first saw generics…

      public class Base

      {

      }

      public class Derived : Base

      {

      }

      public class Tests {

         public static void Test1() {

            List<Derived> derivedList = new List<Derived>();

            List<Base> baseList = derivedList; // sounds fair?

            baseList.Add(new Base()); // wrong, collection is actually of of Derived type, and…

            Derived d = derivedList[0]; // should actually make an implicit up-cast to work (and fail in this case)

         }

      }

    So you cant know, at run time, what type is safe for generic cast, unless you limit this type to use "T" only in input or only in output method parameters.

  32. tytusse says:

    As someone pointed much earlier (so I am not 1st) – the keywords are necessary – you cant now – at runtime – what is the actual generic type of generic collection, and furthermore – using "generic" cast you will loose type information which is necessary for compile time checks and leads to stupid runtime fails, of which I dreamed to forget when I first saw generics…

      public class Base

      {

      }

      public class Derived : Base

      {

      }

      public class Tests {

         public static void Test1() {

            List<Derived> derivedList = new List<Derived>();

            List<Base> baseList = derivedList; // sounds fair?

            baseList.Add(new Base()); // wrong, collection is actually of of Derived type, and…

            Derived d = derivedList[0]; // should actually make an implicit up-cast to work (and fail in this case)

         }

      }

    So you cant know, at run time, what type is safe for generic cast, unless you limit this type to use "T" only in input or only in output method parameters.

  33. 전에 쓴 post에 있는 새로운 IDE 기능은 dynamic과 COM interop에 관련되어 새로 추가된 기능들이고, 당연히 이 밖에도 여러가지 새로 VS10에 추가 되는 IDE

  34. OOP Master says:

    Wow, that’s intense stuff. Thanks for the summary. Sure, I’ll learn about lambduh’s, dynamic and functional C# programming features, but seriously, I haven’t had OOP problems in C# where I even need to know how to spell variance, covariance and contravariance. Oh, and I have personally released hundred’s of thousands of lines of pure C# in production right now for my clients, just one app right now is 557,000 loc that. I bet I could reduce the loc, but at what price, maintainability and readability? No thanks. Small focused classes lead naturally to composition, which is far superior to inheritance for most pattern implementations to get green tests and keep them green.

  35. MVP Factor says:

    Nuestro buen amigo Pete que ya nos ha compartido en el paso muy buenos artículos de XNA, ahora nos comparte

  36. lame guy says:

    I don’t know how some ‘genius’ came up with ‘out’ and ‘in’ when you are talking about inheritance.

    Maybe it was about the limited list of restricted key words in c#, but something like base+derived or sub+super would have made much more sense.

    It’s a shame a not trivial concept was made more difficult by a poor choice of key words.

  37. Ido Ran says:

    Hi,

    Thank you for the post, I really learned a lot.

    Will you be kind to publish a post about the changes in Reflection due to the net covariance changes?

    Specifically, how IsAssignableFrom now behave for open and closed generic types.

    For example:

    Type closedAnimalListType = typeof(List<Animal>);

    Type closedCatListType = typeof(List<Cat>);

    Type openListType = typeof(List<>);

    Type openEnumType = typeof(IEnumerable<>);

    type closedAnimalEnumType = typeof(IEnumerable<Animal>);

    Console.WriteLine(closedAnimalListType.IsAssignableFrom(closedCatListType);

    Console.WriteLine(closedCatListType.IsAssignableFrom(closedAnimalListType);

    Console.WriteLine(openEnumType.IsAssignableFrom(closedCatListType);

    …… All the other combinations – and let us know what easy one will return.

    Thank you,

    Ido

Skip to main content