3

Code:

using System.IO;
using System;
using System.Reflection;
using System.Collections.Generic;

class AnyClass
{
    delegate void Del(string str);
    static void Main()
    {
        List<Del> listDel = new List<Del>();

        listDel.Add(delegate(string str) { });
        Console.WriteLine( listDel[0].Method.ToString() );

        listDel.Add(delegate(string str) { });
        Console.WriteLine( listDel[1].Method.ToString() );

        for (int i = 0; i < 2; i++)
        {
            listDel.Add(delegate(string str) { });
        }
        Console.WriteLine( listDel[2].Method.ToString() );
        Console.WriteLine( listDel[3].Method.ToString() );
    }
}

Output:

Void m__0(System.String)
Void m__1(System.String)
Void m__2(System.String)
Void m__2(System.String)

Why do the delegates instantiated in the loop "point" to the same method (m__2) whereas the ones instantiated outside the loop point to two different methods (m__0 and m__1)?

Is there any way how to instantiate delegates that point to different/unique methods inside a loop?

Example of usage: I need to have delegates as keys in a dictionary, so they need to be unique. Instantiation inside a loop is necessary to provide enough of flexibility.

Riko
  • 433
  • 1
  • 6
  • 20
  • 1
    Hm, this should be reported to the Roslyn team for review. I see no reason why there shouldn't be a *single* method and delegate. – usr May 30 '15 at 12:40
  • @usr I'm not sure he's running on Roslyn. – Yuval Itzchakov May 30 '15 at 12:42
  • @usr Roslyn doesn't [cache it either](http://tryroslyn.azurewebsites.net/#K4Zwlgdg5gBAygTxAFwKYFsDcAoUlaIoYB0AwgPYA2lqAxsmORCMQOKoSoBOYtOADsABGlXjFqUAhiBAwAghASkpM7AG9sMLTAAmqGlEloYAN3JgdMACL7UAChQ9oMRwEoc20+csBZSZDtXTW0NT08TSS4YURQbShgAXhhOAHcYABkwFAAeONQAPkCcYLDorOQ44jkdHTs9AyN7R3wXZC5XGDUYAF93ErCKZipUYgB1HjRMzjsy2P0AbQAGAF1iH1RkAAtyHWIAFXI4NvxAmD7+zxiK/SqauttDNAdj5zdOnr7SrUGQYbGJ1BTeyza6UeYARlW6y2O32hxeUFO5y+MAAZuQonZIMgYGBEjBFphcTBsjAAExEsAAaipQRRoRR2iulWqtXqqEeTQRrXa716Hi+3Qu2h+f3GYEmkGBzIWZKhG22uwORyciI6ny+opo/wlgKlMxlYIAzPKYUr4aqkQKtELukAA==) – Yuval Itzchakov May 30 '15 at 12:50

3 Answers3

2

Why do the delegates instantiated in the loop "point" to the same method (m__2) whereas the ones instantiated outside the loop point to two different methods (m__0 and m__1)?

Because behind the scenes the compiler is caching the delegate creation. When you create the first two delegates, the compiler doesn't have knowledge that they are the same, so he creates two different cached delegates and two named methods. Inside your for loop, the compiler is optimizing by only instantiating the delegate once. He can be certain that it's the same delegate each time, instantiate it once, then cache it.

When you de-compile your code, it actually looks like this:

private delegate void Del(string str);

[CompilerGenerated]
private static Launcher.Del CS$<>9__CachedAnonymousMethodDelegate3;

[CompilerGenerated]
private static Launcher.Del CS$<>9__CachedAnonymousMethodDelegate4;

[CompilerGenerated]
private static Launcher.Del CS$<>9__CachedAnonymousMethodDelegate5;

private static void Main()
{
    List<Launcher.Del> listDel = new List<Launcher.Del>();
    List<Launcher.Del> arg_24_0 = listDel;

    if (Launcher.CS$<>9__CachedAnonymousMethodDelegate3 == null)
    {
        Launcher.CS$<>9__CachedAnonymousMethodDelegate3 = 
                                 new Launcher.Del(Launcher.<Main>b__0);
    }
    arg_24_0.Add(Launcher.CS$<>9__CachedAnonymousMethodDelegate3);

    Console.WriteLine(listDel[0].Method.ToString());

    List<Launcher.Del> arg_5D_0 = listDel;
    if (Launcher.CS$<>9__CachedAnonymousMethodDelegate4 == null)
    {
        Launcher.CS$<>9__CachedAnonymousMethodDelegate4 = 
                                 new Launcher.Del(Launcher.<Main>b__1);
    }
    arg_5D_0.Add(Launcher.CS$<>9__CachedAnonymousMethodDelegate4);

    Console.WriteLine(listDel[1].Method.ToString());
    for (int i = 0; i < 2; i++)
    {
        List<Launcher.Del> arg_9A_0 = listDel;
        if (Launcher.CS$<>9__CachedAnonymousMethodDelegate5 == null)
        {
            Launcher.CS$<>9__CachedAnonymousMethodDelegate5 = 
                                 new Launcher.Del(Launcher.<Main>b__2);
        }
        arg_9A_0.Add(Launcher.CS$<>9__CachedAnonymousMethodDelegate5);
        Console.WriteLine(listDel[2 + i].Method.ToString());
    }
}

[CompilerGenerated]
private static void <Main>b__0(string str)
{
}
[CompilerGenerated]
private static void <Main>b__1(string str)
{
}
[CompilerGenerated]
private static void <Main>b__2(string str)
{
}

I would definitely not rely on a delegate being a proper key for a Dictionary.

Is there any way how to instantiate delegates that point to different/unique methods inside a loop?

You can force the delegate to be a "fresh instance" only by explicitly creating a new Del instance yourself and passing a new named method each time. There are other more "fishy" ways of doing so, but I wouldn't recommend taking those paths just to get a new delegate.

Community
  • 1
  • 1
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • Using delegates as keys for Dictionary is not my choice, I'm using a plugin, where it is used like that. Is there any way how to solve it? Or how to "trick" the compiler so it would create a new one each time inside a loop? – Riko May 30 '15 at 12:26
  • Or does it mean that it is not possible at all? – Riko May 30 '15 at 12:33
  • It isn't possible using an anonymous method or a lambda expression. If you want, you'll need to create a named method and explicitly invoke `new Del(Method)` each time. – Yuval Itzchakov May 30 '15 at 12:35
  • Can you add this comment to your answer as an answer to my second question ("Is there any way how to instantiate delegates that point to different/unique methods inside a loop?")?, then I will mark it as the correct answer – Riko May 30 '15 at 12:39
  • You can create [`DynamicMethods`](https://msdn.microsoft.com/en-us/library/exczf7b9(v=vs.110).aspx). But that would be a hassle... – Kirill Slatin May 30 '15 at 12:41
  • @Riko Edited the answer. – Yuval Itzchakov May 30 '15 at 12:41
  • @Riko, Do you need the reference to Delegate to be unique? or the method it references? – Kirill Slatin May 30 '15 at 12:43
  • @Kirill Slatin The Dictionary uses delegate as the key. Dict I'm not sure what you mean by "reference to Delegate" – Riko May 30 '15 at 12:46
  • @Riko, I mean that delegate is an object – Kirill Slatin May 30 '15 at 17:15
0

Is there any way how to instantiate delegates that point to different/unique methods inside a loop?

You can't make each loop iteration create a different method because methods are hard-coded into the assembly. Their number is fixed while the loop could be unbounded.

You can make each syntactic appearance of a lambda have a different method by using some kind of hack:

Action<int> x = i => {
   if (Environment.CurrentManagedThreadId < 0 /*always false*/)
     Console.WriteLine(i + uniqueIntegerHere);
};

This forces each method body to be unique and the compiler cannot ever optimize this away. You can of course pull the body into a helper method.

If you want unique delegates per loop iteration you either need to create methods at runtime or keep a set of statically compiled methods:

void F1() { }
void F2() { }
void F3() { }
...

T4 templates come to mind.

usr
  • 168,620
  • 35
  • 240
  • 369
  • Any equivalent of Environment.CurrentManagedThreadId for .NET 3 that would do the same job? – Riko May 30 '15 at 13:18
  • Yes, anything that is guaranteed to be opaque to the compiler and fast does the job. You can initialize a static (non readonly!) field to (bool)false and branch off of that as well. Readonly fields are allowed to be inlined by the JIT (and it does that in practice). – usr May 30 '15 at 13:31
  • I'm not sure if I interpreted your point with syntactic appearance of a lambda correctly; but it has to be outside of the loop, right? In this case, I can generate unique methods by just adding them one by one to the list (like m__0 and m__1 in my code). It doesn't generate unique methods when used inside the loop, or does it? – Riko May 30 '15 at 15:08
  • This has nothing to do with looping. Loops are not a special case. The current compiler seems to create one method/delegate per syntactic appearance in the source code. How could the compiler possibly generate a variable number of methods? It does not know how often your loop executes. Anyway, is your problem solved? – usr May 30 '15 at 15:28
  • Ooh, I see now what you mean by syntactic appearance. Then I suppose that what I wanted is just not possible, which means that I need to write a statement for every single time I need to create new delegate. Doesn't really solve the problem, but at least I know it's not possible. – Riko May 30 '15 at 16:03
  • *Or not possible without having to use DynamicMethod – Riko May 30 '15 at 16:38
0

Yet another way similar to the one proposed by @usr. You can force compiler to create a new instance of delegate object using reflection method Delegate.CreateDelegate(type, this, methodInfo). The trick goes at the point where this parameter is always a new object thus forcing myMethod being called on it and thus each delegate actually represents a different context for compiler.

This requires the method for delegation to be inside a separate class, which you can instantiate. I am not sure this requirement fits you actual task. Perhaps you will be inspired for another solution based on this one...

using System.IO;
using System;
using System.Reflection;
using System.Collections.Generic;

class AnyClass
{
    delegate void Del(string str);

    private static Dictionary<Del, string> dict = new Dictionary<Del, string>();
    static void Main()
    {
        List<Del> listDel = new List<Del>();
        int count = 10;
        for (int i = 0; i < count; i++)
        {
            listDel.Add(factory());
            dict.Add(listDel[i ], "Delegate " + (i));
        }
        for (int i = 0; i < count; i++)
        {
            Console.WriteLine(listDel[i].Method.ToString());
            listDel[i].Invoke((i).ToString());
        }

        Console.ReadLine();
    }

    public class DelegateEncapsulator
    {
        private int _number;
        public DelegateEncapsulator(int number)
        {
            _number = number;
        }
        public void myMethod(string str) { 
             Console.WriteLine("Delegate " + _number + " " + str); 
        }
    }

    private static int delegateCounter = 100;
    private static Del factory()
    {
        var obj = new DelegateEncapsulator(delegateCounter++);
        var ret = (Del)Delegate.CreateDelegate(typeof(Del), obj, 
             typeof(DelegateEncapsulator).GetMethod("myMethod"));
        return ret;
    }
}

This code adds all delegates into a dictionary. You can play with number elements to be added.

Hope this helps

Kirill Slatin
  • 6,085
  • 3
  • 18
  • 38
  • How could I use this with more than 2 repetitions inside the loop? Right now it seems that it only solves the problem with exactly 2 repetitions. – Riko May 30 '15 at 14:04
  • @Riko, You're absolutely right. It worked only due to an error in code. I revised the solution completely. However I am not sure the method is worth the goal. There are inconvenient conditions to accomplish what you require. Perhaps, revising the plugin and making a different key for the dictionary would be the optimal way... – Kirill Slatin May 30 '15 at 16:45