简体   繁体   中英

How to instantiate unique delegates using an anonymous method in a loop (in C#)?

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.

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.

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM