locked
Compare of anonymous delegates are not equal RRS feed

  • Question

  • In general, delegates compare to be equal if they are pointing to the same method.  This allows us to be able to subscribe and unsubscribe from events for instance. Even though the delegates reference is different, the overridden Equals logic makes it possible for the delegates to be the same. Anonymous delegates have different results. If I use the following code, the delegates are equal:

     

    static void Main(string[] args)

    {

     

    Action a1 = CreateDelegate(5);

     

    Action a2 = CreateDelegate(5);

     

    bool same = a1 == a2;

    }

     

    static Action CreateDelegate(int value)

    {

     

    return () => {  };

    }

    However, if I use this code, the turn out to be not equal:

     

    static void Main(string[] args)

    {

     

    Action a1 = CreateDelegate(5);

     

    Action a2 = CreateDelegate(5);

     

    bool same = a1 == a2;

    }

     

    static Action CreateDelegate(int value)

    {

     

    return () => { value++; };

    }

    This becomes a real problem for classes that expect to be able to compare two delegates for equality when someone from the outside uses a captured anonymous delegate. You would think that since the parameters are the same, the delegates would come out to be equal. After some digging, I know there is information stored in the Delegate class to be able to check for whether the two method pointers are the same (Delegate._methodPtr), but I shouldn't have to use reflection for this functionality. It would be nice if this functionality was changed, or a method could be provided that you could do this comparison.

    Friday, September 11, 2009 3:42 PM

Answers

All replies

  • I cannot think of any scenario in well-written code where one would need to compare delegates.

    It is akin to testing for the generic type, to see which type is in use, when you use generics.

    The JIT is generating different types for each of those delegates, by the way.

    Mark the best replies as answers. "Fooling computers since 1971."
    Friday, September 11, 2009 4:12 PM
  • The case I have is that we have a thread class that handles executing some type of work via a delegate. There are cases where you would want to collapse that delegate work so that the previously enqueued work is tossed upon a collapse scenario. The default is to collapse based on a delegate comparison so that it is automatic and no extra information needs to be passed. This all worked fine before the anonymous delegate. I do realize that the JIT is generating a new type underneath, but it should also use the same type if the parameter type is identical. Why do all the same class generation and JIT when you don't have to.

    Also...  This code existed prior to anonymous delegates, so it's weird from the standpoint that the code used to work as intended when anonymous delegates did not exist yet. So, from our shoes, the framework introduced functionality that broke existing code.

    Friday, September 11, 2009 6:05 PM
  • Anonymous Methods (C# Programming Guide)

    Read that.  The first paragraph in particular.  Not intended for use with Lamba Expressions.
    BTW, I do not think there is anything that can actually be called an "Anonymous Delegate".  That suggest a new type of delegate.
    The delegate may point to an anonymous method, but that delegate is still the same multi-cast type that it has always been.

    Nothing broke your code. 
    I am forced to question the following claim.

    "Also...  This code existed prior to anonymous delegates, so it's weird from the standpoint that the code used to work as intended when anonymous delegates did not exist yet. So, from our shoes, the framework introduced functionality that broke existing code."

    Why?  Simple.
    In both of your example you use Lambda Expressions, which were not available in the time frame I believe you are suggesting, .NET 2.0.  And what I believe that what you refer to here.  This code existed prior to anonymous delegates, would suggest prior to .NET 2.0.  Again that is prior to the Lambda Expressions present in your samples.

    Hope this helps.

    Rudedog  =8^D

    Mark the best replies as answers. "Fooling computers since 1971."
    Friday, September 11, 2009 10:18 PM
  • The "anonymous delegate" was an improper phrasing for the term anonymous methods I admit, but there is no need to be so picky when you clearly know what was intended. A simple correction would have been well enough. Besides, even the CLR team used the term loosely: http://hoser.lander.ca/2005/12/05/AnonymousDelegatesVsAnonymousMethods.aspx

    The linked article makes no such mention of "Not intended for use with Lambda Expressions." Great detective work to determine that a code example uses a Lambda Expression, which compiles down to an anonymous method and is still possible to run under the 2.0 framework after it is compiled. The fact remains that someone outside of my "black box" implementation is using an anonymous method whether it is a Lambda Expression or the "delegate" syntax. My code remained the same. It doesn't matter that someone is referencing it under the 2.0+ framework. The sample code was just a simple example of how it breaks down. I certainly did not show any of our internal threading code. The collapsing code is meant to make life easier for someone using my API by automatically collapsing work if you are calling the same method. This is not the only way to collapse, just the default and simplest way. I don't appreciate it when people dance around the issue as if I don't know how to write proper code. Like your previous statement, "I cannot think of any scenario in well-written code where one would need to compare delegates." So, just because you haven't thought of a scenario must mean that I am doing something wrong or not writing well-written code. If you ask me, having ideas that others haven't thought of is where innovation happens. Think outside the box...
    Saturday, September 12, 2009 12:57 AM
  • This is from the first paragraph.

    "Anonymous methods enable you to omit the parameter list, and this means that an anonymous method can be converted to delegates with a variety of signatures. This is not possible with lambda expressions."

    Assigning targets for delegates is not recommended through the use of Lamba Expressons.

    The rest of your comments are argumentative, so I shall ignore them and stand by original opinion.

    "I cannot think of any scenario in well-written code where one would need to compare delegates.
    It is akin to testing for the generic type, to see which type is in use, when you use generics.
    "

    The real power of a delegate comes from leveraging the fact that the target is don't care condition.
    The same is true when you leverage generics.  The actual concrete type in use should be a don't care condition.

    Finally, you have already acknowledged that the two methods are seen as different types.
    You seem to have answered your own original question.

    Hope this helps.

    Rudy  =8^D

    Mark the best replies as answers. "Fooling computers since 1971."
    Saturday, September 12, 2009 11:36 AM
  • Hahaha... That is so laughable. Feel free to look at the IL sometime. You'll find that "() => {}" and "delegate {}" are identical. I have no idea why you are on such a Lambda Expression kick, especially since it has no bearing on the topic. And saying that "well-written code" would never have to look at a generic type parameter's concrete type is pretty naive. Sorry to say, the Framework does not account for all possible situations. It sure would be nice to have a way to use the Delegate type for a constraint. Someone who knows what they are talking about can chime in at any time...
    Saturday, September 12, 2009 12:23 PM
  • You are not the first to post about a lack of success when comparing the targets of delegates.

    Any comparison will be 100% unreliable. 
    You seem to have figured this out already.
    But, wish to know why.  Is that correct?

    /*********************************/

    Remarks

    Two delegates with the same methods, the same targets, and the same invocation lists are considered equal, even if they are not both multicast (combinable) or both singlecast (noncombinable).

    The methods and targets are compared for equality as follows:

    • If the two methods being compared are both static and are the same method on the same class, the methods are considered equal and the targets are also considered equal.
    • If the two methods being compared are instance methods and are the same method on the same object, the methods are considered equal and the targets are also considered equal.
    • Otherwise, the methods are not considered to be equal and the targets are also not considered to be equal.

    Two invocation lists are considered identical if they have the same order and the corresponding elements from the two lists represent the same method and target.

    /*********************************************/


    Happy Coding.


    Mark the best replies as answers. "Fooling computers since 1971."

    Saturday, September 12, 2009 12:40 PM
  • I do realize delegate comparison is unreliable in its current state. I wish to know why Microsoft has decided to make it so, whether it was intended or not, and whether it should be posted as a bug since it broke code that could rely on delegate equality prior to anonymous methods. I would expect that if I have a method that creates an anonymous method and captures its state in the wrapper class, that the delegates should compare to be equal. In this case, it makes no difference that they target two different instances to the developer because it is done behind the scenes for you. What they could do, is capture the object reference that is creating the anonymous method in the wrapper class. Then they could test that the references are the same as well as checking the method pointer to determine if two delegates are the same. They could easily have their generated wrapper implement an interface that they could check for in the delegate. Those are just ideas, but it wouldn't be too difficult to determine equivalence.

    In addition, I can write the code to not use an anonymous method and even though I have two separate delegate instances, they correctly compare to be equal. Why should a short hand version that is supposed to save you from writing more code, break that functionality?

    Saturday, September 12, 2009 1:03 PM
  • Another option would be to abstract it even more from the user by having the delegate's Target property actually point to the anonymous method's creator, which would then look identical to the named method delegate. It would just do the right thing underneath by storing all the information the delegate would need for comparison inside the delegate itself, and actually call the wrapper class on invocation.
    Saturday, September 12, 2009 1:26 PM
  • Delegate.Equality Operator (System)

    I posted the above link so that if you look at the Stacks and Heaps link, you might inclined to re-read it.

    It should and does make a differences if you have two different instances of the same type. 
    What if the methods have differnet behaviors?
    You test for equality, it comes up true.  Yet, the delegates do totally different stuff.

    Anonymous methods define a unique type when they are instantiated. 
    The JIT does not know and should not care if the method instances are created by the same block of code.

    Here's something I wrote a while back. 
    It helps to put many developers in the right mind set.
    I have found that many developers are a bit fuzzy on the concepts of "Identity" and "Equivalence".

    Stacks and Heaps.

    With that in mind, does it make it any clearer why it fails when you use Lamda Expressions to define the target.
    The shortcut syntax defines a new type everytime it executes.


    Mark the best replies as answers. "Fooling computers since 1971."
    Saturday, September 12, 2009 1:41 PM
  • I clearly understand how a delegate is compared. I believe what the framework is trying to achieve with anonymous delegates is not quite there yet. They are trying to make this happen underneath so the developer doesn't have to define a method every time. I'm not sure why you brought up about delegates pointing to two different instances of the same type since that should not be equal. Again, we are talking about anonymous methods here, and they should be considered equal if you are reusing the same anonymous method from the same origin. The Lambda Expression used above or delegate syntax certainly does not define a new type every time it executes. It only does once at compile time and creates a new instance per execution. You also wrote:

    "Anonymous methods define a unique type when they are instantiated. 
    The JIT does not know and should not care if the method instances are created by the same block of code."


    You should really re-think that statement. Anonymous methods define a new type at compile time, not runtime. The JIT has no bearing on the problem at all. Again, you have veered off course. I'll probably just post it as a bug and see what Microsoft wants to do about it, since here I am getting nowhere.

    Saturday, September 12, 2009 2:02 PM
  • I do realize delegate comparison is unreliable in its current state. I wish to know why Microsoft has decided to make it so, whether it was intended or not, and whether it should be posted as a bug since it broke code that could rely on delegate equality prior to anonymous methods. I would expect that if I have a method that creates an anonymous method and captures its state in the wrapper class, that the delegates should compare to be equal. In this case, it makes no difference that they target two different instances to the developer because it is done behind the scenes for you. What they could do, is capture the object reference that is creating the anonymous method in the wrapper class. Then they could test that the references are the same as well as checking the method pointer to determine if two delegates are the same. They could easily have their generated wrapper implement an interface that they could check for in the delegate. Those are just ideas, but it wouldn't be too difficult to determine equivalence.

    In addition, I can write the code to not use an anonymous method and even though I have two separate delegate instances, they correctly compare to be equal. Why should a short hand version that is supposed to save you from writing more code, break that functionality?



    I keep bringing that up because you bring it up.
    You just did a 180 and corrected yourself. 
    Now you are pointing in the right direction.

    How is it possible that objects created in a static method at runtime have their types defined at compile time?  It's not.
    That is why I keep referring to the JIT compiler and the creation of types during runtime execution.
    Mark the best replies as answers. "Fooling computers since 1971."
    Saturday, September 12, 2009 2:13 PM
  • LOL. Unbelievable...
    Saturday, September 12, 2009 2:49 PM
  • I submitted it as a bug and provided a possible solution to the problem. We'll see if we get a fix. Please help support the issue.

    https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=489518
    • Marked as answer by Adam Love Monday, September 14, 2009 12:03 PM
    Saturday, September 12, 2009 4:24 PM
  • I guess you didn't read the note about the behavior change from .NET 1.x at the link I gave you.

    Delegate.Equality Operator (System)



    Mark the best replies as answers. "Fooling computers since 1971."
    Saturday, September 12, 2009 4:57 PM
  • I sure did, and it still doesn't matter. They are talking about a different change of equality. Still not relevant to my bug post. Please stop posting useless comments.
    Saturday, September 12, 2009 5:04 PM
  • Hmm, that's going to be closed with "By Design" really quickly.  The closure for the anon delegate needs to capture the value of the "x" argument.  The compiler cannot make the assumption that the "x" value will be the same so must create a new instance of the closure for every invocation and store the value of x.

    Re-using an existing instance would be technically possible.  But that requires a List<WeakReference> to keep track of all instances and comparing the captured values of "x".  That's grossly inefficient, just creating a new instance makes much more sense.  The resulting delegate instances have thus the same Method but not the same Target.

    You can't claim that this is breaking behavior from .NET 1.x, you broke your code by using anonymous methods.  Why don't you just delete that feedback article, I'm sure they've got something better to do.

    Hans Passant.
    Saturday, September 12, 2009 5:10 PM
  • Re-using any existing instance is not something that was suggested so... If you examine the IL, it is no large leap to see that the reference to the object creating the anonymous method (the "this" reference) could be stored in a field of the Delegate created to use for comparison as opposed to using the closure class for comparison. And who said it is my anonymous method code that broke my 1.x code? There is such a thing as plugins or something as simple as a 3rd party using your assembly as a reference.

    You have no right to suggest, "Why don't you just delete that feedback article, I'm sure they've got something better to do." when I am trying to better the product with feedback. Constructive feedback from people would be helpful to everyone.
    Saturday, September 12, 2009 5:29 PM
  • As a test, I used reflection after the delegate creation for an anonymous method to force the Delegate._target to instead point to the class that created the anonymous method. It works perfect. All they would have to do is add stfld to the generated IL and problem solved. Obviously the _target would need to be set to null if the anonymous method is created in a static method.
    • Marked as answer by Adam Love Monday, September 14, 2009 12:02 PM
    • Unmarked as answer by Adam Love Monday, September 14, 2009 12:03 PM
    Saturday, September 12, 2009 8:42 PM