views:

387

answers:

5

Consider the following example code:

static void Main(string[] args)
{
   bool same = CreateDelegate(1) == CreateDelegate(1);
}

private static Action CreateDelegate(int x)
{
   return delegate { int z = x; };
}

You would imagine that the two delegate instances would compare to be equal, just as they would when using the good old named method approach (new Action(MyMethod)). They do not compare to be equal because the .NET Framework provides a hidden closure instance per delegate instance. Since those two delegate instances each have their Target properties set to their individual hidden instance, they do not compare. One possible solution is for the generated IL for an anonymous method to store the current instance (this pointer) in the target of the delegate. This will allow the delegates to compare correctly, and also helps from a debugger standpoint since you will see your class being the target, instead of a hidden class.

You can read more about this issue in the bug I submitted to Microsoft. The bug report also gives an example of why we are using this functionality, and why we feel it should be changed. If you feel this to be an issue as well, please help support it by providing rating and validation.

https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=489518

Can you see any possible reason why the functionality should not be changed? Do you feel this was the best course of action to get the issue resolved, or do you recommend that I should take a different route?

+14  A: 

I'm not so inclined to think this is a "bug". It appears moreover that you're assuming some behaviour in the CLR that simply does not exist.

The important thing to understand here is that you are returning a new anonymous method (and initialising a new closure class) each time you call the CreateDelegate method. It seems that you are experting the delegate keyword to use some sort of pool for anonymous methods internally. The CLR certainly does not do this. A delegate to the anonymous method (as with a lambda expression) is created in memory each time you call the method, and since the equality operator does of course compare references in this situation, it is the expected result to return false.

Although your suggested behaviour may have some benefits in certain contexts, it would probably be quite complicated to implement, and would more likely lead to unpredictable scenarios. I think the current behaviour of generating a new anonymous method and delegate on each call is the right one, and I suspect this is the feedback you will get on Microsoft Connect as well.

If you are quite insistent on having the behaviour you described in your question, there is always the option of memoizing your CreateDelegate function, which would insure that the same delegate is returned each time for the same parameters. Indeed, because this is so easy to implement, it is probably one of the several reasons why Microsoft did not consider implementing it in the CLR.

Noldorin
Lambda expressions have nothing to do with this. Look at the IL. I'm not suggesting any kind of crazy pooling or anything of the sort. I'm simply suggesting that taking implementation aside, how would you, as the developer writing the code, want it to work knowing what you know about the comparison of a delegate. Forget about the implementation details of anonymous methods. I simply want to change it for the better of the developer community and the Framework.
BigUnit
@BigUnit: Lambda expressions have a lot to do with this. It is precisely because they are generated on the fly that you are experiencing this confusion. Personally, I'm with the .NET Framework devs on this one. If I wrote the code given in the above question, the actual result would be my expected result. Of course, one can hardly classify this 'intuitive' behaviour, but I think you will see it is perfectly reasonably behaviour once you wrap your head around the internal workings of the CLR.
Noldorin
@Noldorin: Sorry, I still don't see where the lambda expression fits in. There is no LE in my code, nor in the produced IL. There is certainly no runtime on the fly generation, if that is what you are getting at.
BigUnit
@Big Unit: My bad. I'm in the habit of using lambda expressions rather than anonymous methods. For the most part, you can treat them as virtually the same thing (though there are certainly some subtle differences). Anyway, the same arugment applies to anonymous methods. And yes, a class is being generated for the closure at runtime. Not in the dynamic sense of course; the class itself is generated by the compiler (JIT?) and simply instantiated on the fly. Either way, a new instance of the hidden class/closure is created every time you call the function, which explains why the refs are inequal.
Noldorin
That is correct. The class is already generated ahead of execution though. The references are certainly not equal. In their MSDN information about anonymous methods they state, "By using anonymous methods, you reduce the coding overhead in instantiating delegates by eliminating the need to create a separate method." But that would not be true if I can't count on equality for my implementation. If they would expose a different method of equality to determine if the signatures are equal and I could require an instance to be passed into me. The Method prop is populated via reflection, so yuck.
BigUnit
@BigUnit: The default equality operator for all objects compares references - the only way this can be changed is by overloading it - and this is of course not possible with delegates. I doubt the CLR team would care to change such a fundamental aspect of .NET for such a comparatively small reason as this. So what's wrong with memoization, anyway? As far as I can see, that solves your problem quite well.
Noldorin
@Noldorin: The equality operator has already been overloaded for delegates. `same` would be true if no variables had to be captured. (In fact, in this case only one delegate would be created and then cached, but if it captured just a `this` reference for the class then you'd see genuine delegate equality being tested by ==.)
Jon Skeet
@Jon Skeet: Not sure I quite understand you there. Mind elaborating/pointing to the appropiate bit of the CLR spec?
Noldorin
+3  A: 

I don't know about the C# specific details of this problem but I worked on the VB.Net equivalent feature which has the same behavior.

The bottom line is this behavior is "By Design" for the following reasons

The first is that in this scenario a closure is unavoidable. You have used a piece of local data within an anonymous method and hence a closure is necessary to capture the state. Every call to this method must create a new closure for a number of reasons. Therefore each delegate will point to an instance method on that closure.

Under the hood a anonymous method / expression is represented by a System.MulticastDelegate derived instance in code. If you look at the Equals method of this class you will notice 2 important details

  • It is sealed so there is no way for a derived delegate to change the equals behavior
  • Part of the Equals method does a reference comparison on the objects

This makes it impossible for 2 lambda expressions which are attached to different closures to compare as equals.

JaredPar
I agree that today the Framework makes it impossible to compare the anonymous methods. Just because they point to two different instances doesn't mean they can't compare to be equivalent. Sure it would mean a change, but I certainly wouldn't claim it to be impossible so quickly.
BigUnit
@BigUnit, I'm saying it's impossible given the current state of the BCL. At the same time I also agree the behavior of the BCL is correct. Two instance methods on two objects shouldn't ever compare to be equal.
JaredPar
@JaredPar: I understand what you are saying, but it is abstracted away from you. You forgot to mention that the two objects are hidden from the developer. The fact it captures state is just to make it easier on the developer. Give me one good example of existing code that would break from changing this functionality? It sucks there is no good work around to this problem that makes it simple for the person using the API except to not use anonymous methods and be forced to use the Func/Action paradigm. I would then be forced to create the same number of generic classes in my situation, about 30.
BigUnit
A: 

I can't think of a situation where I've ever needed to do that. If I need to compare delegates I always use named delegates, otherwise something like this would be possible:

MyObject.MyEvent += delegate { return x + y; };

MyObject.MyEvent -= delegate { return x + y; };

This example isn't great for demonstrating the issue, but I would imagine that there could be a situation where allowing this could break existing code that was designed with the expectation that this is not allowed.

I'm sure there are internal implementation details that also make this a bad idea, but I don't know exactly how anonymous methods are implemented internally.

Dan Herbert
Nobody currently does anything like that because the results would usually be false. But here is a case where the Framework allows such a result:If I have a method like this:public Action CreateDelegate(){ return delegate { CallSomeMethod(); };}And then use that function for my event hookup/removal:MyEvent += CreateDelegate();MyEvent -= CreateDelegate();Works without a problem. Even currently there are cases where they will allow them to be equivalent. So, there is no way to write code to determine equivalence today.
BigUnit
+2  A: 

EDIT: Old answer left for historical value below the line...

The CLR would have to work out the cases in which the hidden classes could be considered equal, taking into account anything that could be done with the captured variables.

In this particular case, the captured variable (x) isn't changed either within the delegate or in the capturing context - but I'd rather the language didn't require this sort of complexity of analysis. The more complicated the language is, the harder it is to understand. It would have to distinguish between this case and the one below, where the captured variable's value is changed on each invocation - there, it makes a great deal of difference which delegate you call; they are in no way equal.

I think it's entirely sensible that this already-complex situation (closures are frequently misunderstood) doesn't try to be too "clever" and work out potential equality.

IMO, you should definitely take a different route. These are conceptually independent instances of Action. Faking it by coercing the delegate targets is a horrible hack IMO.


The problem is that you're capturing the value of x in a generated class. The two x variables are independent, so they're unequal delegates. Here's an example demonstrating the independence:

using System;

class Test
{
    static void Main(string[] args)
    {
        Action first = CreateDelegate(1);
        Action second = CreateDelegate(1);
        first();
        first();
        first();
        first();
        second();
        second();
    }

    private static Action CreateDelegate(int x)
    {
        return delegate 
        { 
            Console.WriteLine(x);
            x++;
        };
    }
}

Output:

1
2
3
4
1
2

EDIT: To look at it another way, your original program was the equivalent of:

using System;

class Test
{
    static void Main(string[] args)
    {
        bool same = CreateDelegate(1) == CreateDelegate(1);
    }

    private static Action CreateDelegate(int x)
    {
        return new NestedClass(x).ActionMethod;
    }

    private class Nested
    {
        private int x;

        internal Nested(int x)
        {
            this.x = x;
        }

        internal ActionMethod()
        {
            int z = x;
        }
    }
}

As you can tell, two separate instances of Nested will be created, and they will be the targets for the two delegates. They are unequal, so the delegates are unequal too.

Jon Skeet
I think the OP understands this (well enough), and rather his main question relates to *why* this is the behaviour of the CLR.
Noldorin
@Noldorin: Yup, have reread question and reanswered.
Jon Skeet
I do understand why it currently doing what it does. You said, "The more complicated the language is, the harder it is to understand." However, I am not proposing any change to the language itself, but to the underlying generated IL that is already done via compiler. The Framework in a sense is made to make life easier, so why make it more difficult when we don't have to? Changing the Target isn't the best answer, there needs to be a better way. It could store it as a different field in the delegate, or the capture class could store it in a way that the delegate could access for comparison.
BigUnit
@BigUnit - think about it ... if you had two methods that have the same signature, how do you expect the compiler to really know what you want? You use the name, and the namespace if necessary. Imagine if the compiler replaces the method name with the identical name, because they are the same in signature, and calls the wrong method? Just because they have the same signature doesn't mean they are identical. Even if the body is the same, there can be side effects that the compiler doesn't know about.
Richard Hein
@Richard Hein: Anonymous methods are a special case. I made no suggestion to leave all instances out of the picture for comparison of equality.
BigUnit
@BigUnit: You *are* proposing a change to the language. You're proposing that it should guarantee delegate equality in the case where a variable is captured but then never changed, either by the surrounding method or the delegate. If you're *not* proposing that it's guaranteed (which means a change to the language specification) then it's even less desirable IMO. I wouldn't expect you to rely on something that isn't guaranteed. If the language were changed somehow to ensure that the value *wasn't* changed (e.g. it was a variable declared readonly) that would make more sense.
Jon Skeet
@Jon Skeet: I did not intend to only guarantee equality in the case where the value is not changed either in or out of the delegate. Just like a named method delegate where you can change the parameter value before/during/after the method and can still guarantee equality for instances of that delegate. If the anonymous method bodies are different, a different closure type is generated so that case should not and would not compare to be equivalent.
BigUnit
@BigUnit: I'm not talking about different anonymous method bodies though. Look at my first example - those two delegates are definitely not equal, as they maintain different (independent) state. You'd need to be able to differentiate between that example and yours - and the only difference is that in your case, there's nothing which will ever change the value of `x`.
Jon Skeet
@Jon Skeet: Read this article and see that AM's are described by MS as a drop-in replacement for named methods: http://msdn.microsoft.com/en-us/library/0yw3tz5k(VS.80).aspx Obviously, anonymous methods are not functionally equivalent to named methods. The lack of equivalence and the inability to differentiate between the two breaks pre-existing 1.x Framework code when someone starts using anonymous methods from a 2.0+ assembly. Therefore when writing a method that receives a delegate as a parameter, you must implement your code using the least common denominator of functionality between them.
BigUnit
@Jon Skeet: I saw your last comment and I disagree. Consider if the CreateDelegate method was an interface instead. As a consumer of that interface, you would expect Equals to return true. The only reason you don't see it this way is because you have white box information.
BigUnit
No I wouldn't. I would expect Equals to return false, because they are independent instances which *shouldn't* be treated as equal. In the case of my sample code, they're effectively different sequences of numbers. Using one when you mean to use the other will give you the wrong results: *they're different*. In most cases of course, anonymous methods *are* drop-in replacements: testing for delegate equality is relatively rare, IMO. Where in the article does MS use the phrase "drop-in replacement" btw? I can't see it.
Jon Skeet
I tend to think pretty conservatively about equality, in fact. If you have different instances of *mutable* types, I would tend to regard them as non-equal - or at the very least, treating them as equal can cause significant problems if they're then mutated and become non-equal. Varying equality is simply problematic.
Jon Skeet
Can we at least agree that they could have a way to test for lesser equality that would not take into account the captured state? They could implement it with a derived type from MulticastDelegate such as AnonymousDelegate. That wouldn't break pre-existing implementations. Then I would also need to be able to get the original instance that created the anonymous method from the delegate. This would allow me to achieve the functionality that I want.
BigUnit
A: 

This behaviour makes sense because otherwise anonymous methods would get mixed up (if they had the same name, given the same body).

You could change your code to this:

static void Main(){   
 bool same = CreateDelegate(1) == CreateDelegate(1);
}

static Action<int> action = (x) => { int z = x; };

private static Action<int> CreateDelegate(int x){
 return action;
}

Or, preferably, since that's a bad way to use it (plus you were comparing the result, and Action doesn't have a return value ... use Func<...> if you want to return a value):

static void Main(){
 var action1 = action;
 var action2 = action;
 bool same = action1 == action2;  // TRUE, of course
}

static Action<int> action = (x) => { int z = x; };
Richard Hein