Co- and Contravariance bugs in .NET 4.0

前端 未结 4 1928
没有蜡笔的小新
没有蜡笔的小新 2020-12-08 06:54

Some strange behavior with the C# 4.0 co- and contravariance support:

using System;

class Program {
  static void Foo(object x) { }
  static void Main() {
          


        
相关标签:
4条回答
  • 2020-12-08 07:17

    One difficulty with delegate combination is that unless one specifies which operand is supposed to be the subtype and which is supertype, it's not clear what type the result should be. It is possible to write a wrapper factory which will convert any delegate with a specified number of arguments and pattern of byval/byref into a supertype, but calling such a factory multiple times with the same delegate would yield different wrappers (this could play havoc with event unsubscription). One could alternatively create a versions of Delegate.Combine which would coerce the right-side delegate to the left delegate's type (as a bonus, the return wouldn't have to be typecast), but one would have to write a special version of delegate.remove to deal with it.

    0 讨论(0)
  • 2020-12-08 07:29

    This solution was originally posted by cdhowie for my question: Delegate conversion breaks equality and unables to disconnect from event but is appears to solve the problem of covariance and contravariance in context of multicast delegates.

    You first need a helper method:

    public static class DelegateExtensions
    {
        public static Delegate ConvertTo(this Delegate self, Type type)
        {
            if (type == null) { throw new ArgumentNullException("type"); }
            if (self == null) { return null; }
    
            if (self.GetType() == type)
                return self;
    
            return Delegate.Combine(
                self.GetInvocationList()
                    .Select(i => Delegate.CreateDelegate(type, i.Target, i.Method))
                    .ToArray());
        }
    
        public static T ConvertTo<T>(this Delegate self)
        {
            return (T)(object)self.ConvertTo(typeof(T));
        }
    }
    

    When you have a delegate:

    public delegate MyEventHandler<in T>(T arg);
    

    You can use it while combining delegates simply by converting a delegate do desired type:

    MyEventHandler<MyClass> handler = null;
    handler += new MyEventHandler<MyClass>(c => Console.WriteLine(c)).ConvertTo<MyEventHandler<MyClass>>();
    handler += new MyEventHandler<object>(c => Console.WriteLine(c)).ConvertTo<MyEventHandler<MyClass>>();
    
    handler(new MyClass());
    

    It supports also disconnecting from event the same way, by using ConvertTo() method. Unlike using some custom list of delegates, this solution provides thread safety out of the box.

    Complete code with some samples you can find here: http://ideone.com/O6YcdI

    0 讨论(0)
  • 2020-12-08 07:35

    Long story short: delegate combining is all messed up with respect to variance. We discovered this late in the cycle. We're working with the CLR team to see if we can come up with some way to make all the common scenarios work without breaking backwards compatibility, and so on, but whatever we come up with will probably not make it into the 4.0 release. Hopefully we'll get it all sorted out in some service pack. I apologize for the inconvenience.

    0 讨论(0)
  • 2020-12-08 07:42

    Covariance and contravariance specifies inheritance relation between generic types. When you have covariance & contravariance, the classes G<A> and G<B> may be in some inheritance relationship depending on what A and B is. You can benefit from this when calling generic methods.

    However, the Delegate.Combine method is not generic and the documentation clearly says when the exception will be thrown:

    ArgumentException- Both a and b are not null reference (Nothing in Visual Basic), and a and b are not instances of the same delegate type.

    Now, Action<object> and Action<string> are certainly instances of a different delegate type (even though related via inheritance relationship), so according to the documentation, it will throw an exception. It sounds reasonable that the Delegate.Combine method could support this scenario, but that's just a possible proposal (obviously this wasn't needed until now, because you cannot declare inherited delegates, so before co/contra-variance, no delegates had any inheritance relationship).

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