Event and delegate contravariance in .NET 4.0 and C# 4.0

后端 未结 4 1623
离开以前
离开以前 2020-12-09 02:45

While investigating this question I got curious about how the new covariance/contravariance features in C# 4.0 will affect it.

In Beta 1, C# seems to disagree with t

相关标签:
4条回答
  • 2020-12-09 03:13

    The following code actually works if you have the delegates normalized to the same type from within upon attaching/detaching! I didn't expect detaching to work but it does. It may not be the most optimized solution but it is clean and simple and it solves a serious problem I've had with my Foundation where co-variance is important for example IObservableList : IObservableListOut. At some point extensions created different delegate types depending on context T where T can be 2 different interfaces per context and therefore the delegates created ended up of different type. It wasn't until today that I actually understood how this issue resulted. Detaching won't work with plain "- value".

        private event MyEventHandler<T> happened;
        public event MyEventHandler<T> Happened
        {
            add => this.happened += new MyEventHandler<T>(value);
            remove => this.happened -= new MyEventHandler<T>(value);
        }
    
    0 讨论(0)
  • 2020-12-09 03:15

    Very interesting. You don't need to use events to see this happening, and indeed I find it simpler to use simple delegates.

    Consider Func<string> and Func<object>. In C# 4.0 you can implicitly convert a Func<string> to Func<object> because you can always use a string reference as an object reference. However, things go wrong when you try to combine them. Here's a short but complete program demonstrating the problem in two different ways:

    using System;
    
    class Program
    {    
        static void Main(string[] args)
        {
            Func<string> stringFactory = () => "hello";
            Func<object> objectFactory = () => new object();
    
            Func<object> multi1 = stringFactory;
            multi1 += objectFactory;
    
            Func<object> multi2 = objectFactory;
            multi2 += stringFactory;
        }    
    }
    

    This compiles fine, but both of the Combine calls (hidden by the += syntactic sugar) throw exceptions. (Comment out the first one to see the second one.)

    This is definitely a problem, although I'm not exactly sure what the solution should be. It's possible that at execution time the delegate code will need to work out the most appropriate type to use based on the delegate types involved. That's a bit nasty. It would be quite nice to have a generic Delegate.Combine call, but you couldn't really express the relevant types in a meaningful way.

    One thing that's worth noting is that the covariant conversion is a reference conversion - in the above, multi1 and stringFactory refer to the same object: it's not the same as writing

    Func<object> multi1 = new Func<object>(stringFactory);
    

    (At that point, the following line will execute with no exception.) At execution time, the BCL really does have to deal with a Func<string> and a Func<object> being combined; it has no other information to go on.

    It's nasty, and I seriously hope it gets fixed in some way. I'll alert Mads and Eric to this question so we can get some more informed commentary.

    0 讨论(0)
  • 2020-12-09 03:23

    I just had to fix this in my application. I did the following:

    // variant delegate with variant event args
    MyEventHandler<<in T>(object sender, IMyEventArgs<T> a)
    
    // class implementing variant interface
    class FiresEvents<T> : IFiresEvents<T>
    {
        // list instead of event
        private readonly List<MyEventHandler<T>> happened = new List<MyEventHandler<T>>();
    
        // custom event implementation
        public event MyEventHandler<T> Happened
        {
            add
            {
                happened.Add(value);
            }
            remove
            {
                happened.Remove(value);
            }
        }
    
        public void Foo()
        {
            happened.ForEach(x => x.Invoke(this, new MyEventArgs<T>(t));
        }
    }
    

    I don't know if there are relevant differences to regular multi-cast events. As far as I used it, it works ...

    By the way: I never liked the events in C#. I don't understand why there is a language feature, when it doesn't provide any advantages.

    0 讨论(0)
  • 2020-12-09 03:27

    Are you getting the ArgumentException from both? If the exception is being thrown by just the new handler, then I would think that it's backward-compatible.

    BTW, I think you have your comments mixed up. In C# 3.0 this:

    button.Click += new EventHandler<EventArgs>(button_Click); // old

    wouldn't have run. That's c#4.0

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