【问题标题】:How to instantiate unique delegates using an anonymous method in a loop (in C#)?如何在循环中使用匿名方法(在 C# 中)实例化唯一委托?
【发布时间】:2015-05-30 12:08:20
【问题描述】:

代码:

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() );
    }
}

输出:

Void m__0(System.String)
无效 m__1(System.String)
无效 m__2(System.String)
void m__2(System.String)

为什么在循环中实例化的委托“指向”同一个方法(m__2),而在循环外实例化的委托指向两个不同的方法(m__0m__1)?

有什么方法可以实例化指向不同/唯一方法循环内的委托?

使用示例:我需要将委托作为字典中的键,因此它们必须是唯一的。循环内的实例化对于提供足够的灵活性是必要的。

【问题讨论】:

  • 嗯,这应该报告给 Roslyn 团队进行审查。我看不出为什么不应该有一个 single 方法和委托。
  • @usr 我不确定他是否在 Roslyn 上运行。
  • @usr Roslyn 没有cache it either

标签: c# reflection delegates anonymous-function


【解决方案1】:

为什么在循环中实例化的代表“指向”同一个 方法 (m__2) 而在循环外实例化的方法指向 两种不同的方法(m__0 和 m__1)?

因为编译器在后台缓存委托创建。当您创建前两个委托时,编译器不知道它们是相同的,因此他创建了两个不同的缓存委托和两个命名方法。在您的for 循环中,编译器通过仅实例化委托一次来进行优化。他可以确定每次都是同一个委托,实例化一次,然后缓存。

当你反编译你的代码时,它实际上是这样的:

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)
{
}

我肯定会用not rely on a delegate being a proper key 换一个Dictionary

有什么方法可以实例化指向的委托 循环中的不同/独特方法?

您只能通过自己显式创建new Del 实例并每次传递一个新的命名方法来强制委托成为“新实例”。还有其他更“可疑”的做法,但我不建议仅仅为了获得新的代表而采取这些方式。

【讨论】:

  • 使用代表作为字典的键不是我的选择,我正在使用一个插件,它就是这样使用的。有什么办法可以解决吗?或者如何“欺骗”编译器,让它每次在循环中创建一个新的?
  • 还是说根本不可能?
  • 不可能使用匿名方法或 lambda 表达式。如果需要,您需要创建一个命名方法并每次显式调用 new Del(Method)
  • 您能否将此评论添加到您的答案中,作为对我的第二个问题的回答(“有什么方法可以实例化指向循环内不同/唯一方法的委托?”)?然后我将其标记为正确答案
  • 您可以创建DynamicMethods。但这会很麻烦......
【解决方案2】:

有什么方法可以实例化指向循环内不同/唯一方法的委托?

您不能让每个循环迭代都创建不同的方法,因为方法是硬编码到程序集中的。它们的数量是固定的,而循环可以是无限的。

您可以通过使用某种 hack 使 lambda 的每个语法外观具有不同的方法:

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

这迫使每个方法体都是唯一的,编译器永远无法优化它。您当然可以将 body 拉入辅助方法中。

如果您希望每次循环迭代都有唯一的委托,您需要在运行时创建方法或保留一组静态编译的方法:

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

想到了 T4 模板。

【讨论】:

  • 任何等效于 .NET 3 的 Environment.CurrentManagedThreadId 可以做同样的工作?
  • 是的,任何保证对编译器不透明且快速的东西都可以完成这项工作。您可以将静态(非只读!)字段初始化为 (bool)false 并从中分支出来。 JIT 允许内联只读字段(实际上是这样做的)。
  • 我不确定我是否正确地用 lambda 的语法外观解释了你的观点;但它必须在循环之外,对吧?在这种情况下,我可以通过将它们一一添加到列表中来生成独特的方法(如我的代码中的 m__0 和 m__1 )。在循环内使用时它不会生成唯一的方法,或者是吗?
  • 这与循环无关。循环不是特例。当前的编译器似乎在源代码中为每个语法外观创建一个方法/委托。编译器怎么可能生成可变数量的方法?它不知道您的循环执行的频率。无论如何,你的问题解决了吗?
  • 哦,我现在明白你所说的句法外观是什么意思了。然后我想我想要的就是不可能的,这意味着我需要为每次需要创建新委托时编写一个语句。并不能真正解决问题,但至少我知道这是不可能的。
【解决方案3】:

另一种方式类似于@usr 提出的方式。您可以强制编译器使用反射方法Delegate.CreateDelegate(type, this, methodInfo) 创建委托对象的新实例。诀窍在于this 参数始终是一个新对象,从而强制在其上调用myMethod,因此每个委托实际上代表编译器的不同上下文。

这要求委托方法位于一个单独的类中,您可以对其进行实例化。我不确定此要求是否适合您的实际任务。也许您会受到启发,寻找基于此的另一种解决方案...

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;
    }
}

此代码将所有委托添加到字典中。您可以使用要添加的数字元素。

希望对你有帮助

【讨论】:

  • 如何在循环内重复超过 2 次的情况下使用它?现在看来,它只解决了恰好 2 次重复的问题。
  • @Riko,你说得对。它仅由于代码错误而起作用。我完全修改了解决方案。但是我不确定该方法是否值得目标。有不方便的条件来完成您的要求。也许,修改插件并为字典制作不同的键将是最佳方式......
猜你喜欢
  • 1970-01-01
  • 2010-09-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-01
  • 2010-11-01
  • 1970-01-01
相关资源
最近更新 更多