ATBG: Delegates and structural identity

Today on the Coverity Development Testing Blog’s continuing series Ask The Bug Guys I’ll discuss why it is that two delegates that look the same are not actually assignment compatible, and how to work around this restriction.


As always, if you have questions about a bug you’ve found in a C, C++, C# or Java program that you think would make a good episode of ATBG, please send your question along with a small reproducer of the problem to TheBugGuys@Coverity.com. We cannot promise to answer every question or solve every problem, but we’ll take a selection of the best questions that we can answer and address them on the dev testing blog every couple of weeks.

Advertisements

19 thoughts on “ATBG: Delegates and structural identity

  1. A nastier aspect of delegates arises with contravariance; variables or parameters of a type like `Action` will implicitly accept an `Action`, and it would be possible to write a generic method which would act like `Delegate.Combine` which could combine an `Action` and `Action` into a multicast `Action`, but because `Delegate.Combine` does not accept a type parameter for the resulting delegate, it will fail if given references to delegates of different types, even if those delegates were passed using references of the same type. This can create some severe problems with events. If an event is declared using a contravariant type and the first subscribers attach delegates of a different-but-compatible type, those early subscription attempts will succeed, but will cause any later subscription attempts using the actual declared type to fail.

  2. I’m going to sound a note of disagreement with you there, Eric, where you say if you were doing the type system again things might be different. I don’t like structural typing, or duck typing or whatever you like to call it, when I’m writing robust production code.

    I want the compiler to tell me that I’ve screwed up and structural typing works against that. If I write two delegates, I have a reason why and I’d prefer the compiler only convert one to the other when I know about it.

    If I don’t have a reason, which happens often enough, I won’t write a delegate at all. I’ll just use Action or Func which are great.

    So I think the type system is good now, in this respect.

    • I would agree, if we could specify methods declaration with delegates, ex: instead of “static bool IsDigit(char value){/*…*/}” we could do static Predicate IsDigit{/*…*/} with some (but enough, not just the handfull that actually exists) delegates predefined so we could specify what is the porpouse of the method, even in this case, the conversion from one delegate to another should be allowed, at least explicit, without creating another indirection.

    • There’s definitely usefulness to having distinctions between delegate types with compatible signatures, but there would *also* be usefulness to being able to have a type can hold e.g. a reference to any function which, given any `Animal`, will produce a `Vehicle`.

      Actually, the way I’d most like to see that handled would be for the Runtime to auto-generate on demand members of an infinite family of interfaces with names based upon the number of parameters and combination of byrefs (e.g. `IFuncVVRV` would have one member `TResult Invoke(T1 p1, T2 p2, ref T3 p3, T4 p4);`, and all delegates would be regarded as implementing the appropriate interfaces. In that case, code which wanted to use delegates based upon signature could do so.

      BTW, I’d also like to have the runtime generate for each delegate that doesn’t have `ref` parameters type a `Bind` method which would have the same parameters as the delegate, and which would return either an `Action` or a `Func` which, when invoked, would call the bound delegate with the bound parameters. I would have rather seen that than `Begininvoke`; IMHO, an action like `someDelegate.BeginInvoke(param1,param2)` should have been `ThreadPool.BeginInvoke(someDelegate.Bind(param1,param2));`

  3. Pingback: Dew Drop – June 19, 2014 (#1800) | Morning Dew

  4. If you were doing the type system again, do you think you would you consider adding actual function types, ie. int => bool, instead of requiring a named delegate type?

    • No need to change the type system, there already are non-delegate function types : Func and Action. The only thing that may be lacking is a “int => bool” syntax.

    • One of the problems described there that implicit conversions of delegates with the same signature can solve is old pre-Func and Action library classes that took named delegates as parameters.

      Specifically, you can’t pass an Action to Thread.Start, because it takes another named delegate type.

      I’d suggest that it would be straightforward to just update the library. Give Thread.Start a new overload or something with Action in it. Thread.Start doesn’t really need to care about the particular delegate type, it just wants to know what code to execute and it is happy.

      On the other hand, async methods that follow the Begin…() End…() pattern do care, IMHO. I would like to compiler to give an error if I pass in a completion method that was originally written for some other begin call (eg, I’m editing pre-existing code, or I just auto-complete wrong). It could now, if the class declared its own delegate type instead of AsyncCallback. This change would take that away.

  5. When I read the headline, I thought this was about the somewhat unfortunate Equals behaviour of delegates. That is, two delegates are considered equal if they are “of the same type and share the same targets, methods, and invocation list”.

    That sounds pretty reasonable until you start to do some more interesting things. Let’s say that you want to implement an event by delegating the add and remove accessors to some other event with compatible-but-different delegate type:

    add { OriginalEvent += new MyAction(value); }
    remove { OriginalEvent -= new MyAction(value); }

    However, this won’t always work as expected. I wrote up an example here: http://ideone.com/eArKxq

    Every time the example program refers to the PrintFoo method where a delegate is expected, a new delegate is automatically created which invokes PrintFoo. Those delegates are equal, but they are not identical. This means the adding and removing works fine for OriginalEvent, but when doing it through DelegatingEvent another layer of indirection is added to make the delegate type fit. Now because of how equality is defined, *wrapping two equal but not identical delegates creates two unequal delegates!*

    In order to create a delegating event that actually behaved transparently, I had to use an obscure helper function which manually copies the invocation list of a delegate to create a new one.

    Just wanted to get that out there because I thought it might be interesting for others 🙂

    • IMHO, the main difficulties stems from the decision to handle event unsubscription by searching the event list for a supplied delegate, rather than having the act of subscribing return a token that would be used for unsubscription. This, combined with the “flattening” of combined multicast delegates, means that a multicast delegate doesn’t really act like a single delegate. If delegate Y is subscribed to an event, followed multicast delegate XY, then unsubscribing Y will leave YX subscribed to the event, and attempting to unsubscribe XY will have no effect. Other than event subscription, I can’t think of many places where delegates would need to use anything other than direct referential equality.

      [note: one advantage of using tokens for unsubscription is that if each token holds a reference to the subscriber, unsubscription would not require a linear search, nor would it require any complex mechanism to ensure thread safety; unsubscription could be handled simply by invalidating the reference contained in the token, and ensuring that the subscription list is sometimes cleaned out when new subscriptions are added.]

      • I agree. The event model in C# has often been a point of pain for me, and unsubscription tokens would have solved most of it.

        An event has a collection of subscribers for which the programmer only has two valid operations – add new item, and delete an item that they have to have a reference to.

        That’s a pretty limited collection. I have sometimes resorted to maintaining a separate collection of subscribers that I can query properly, so I have the right reference to remove when removal time came.

        I’d like to be able to do more – delete all of them, see what there is, debug it.

        • Among the things I like about the “unsubscription token” model: (1) Events only need expose a single method rather than a pair, since the method can return an unsubscribe token; (2) As noted, unsubscription doesn’t have to immediately do anything to the list; (3) Weak events may easily be created by wrapping an unsubscribe token in a WeakSubscriptionToken with a finalizer that would call the unsubscribe method; (4) One can ensure that removing a subscription will leave the list in the same state as if the subscription had never been added, even when using multicast delegates, and even when other operations have occurred in the interim.

          I’m curious what scenarios were evaluated when Microsoft chose to implement events and multicast delegates as they did; the implementation of multicast delegates includes some features apparently aimed at optimizing performance, but some aspects are clearly sub-optimal.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s