“Uncurrying”.NET中的实例方法

您是否可以创建实例方法的委托而无需在创建时指定实例? 换句话说,你可以创建一个“静态”委托,它应该调用该方法的实例作为它的第一个参数吗?

例如,如何使用reflection构造以下委托?

Func = i=>i.ToString(); 

我知道我可以使用methodInfo.Invoke,但速度较慢,并且在调用之前不检查类型正确性。

当您拥有特定静态方法的MethodInfo时,可以使用Delegate.CreateDelegate(delegateType, methodInfo)构造委托,并且静态方法的所有参数都保持空闲。

正如Jon Skeet指出的那样,如果方法在引用类型上是非虚拟的,则可以简单地应用相同的方法来创建实例方法的开放委托。 决定在虚拟方法上调用哪个方法很棘手,因此不是那么简单,值类型看起来根本不起作用。

对于值类型, CreateDelegate表现出非常奇怪的行为:

 var func37 = (Func)(37.ToString); var toStringMethod = typeof(int).GetMethod("ToString", BindingFlags.Instance | BindingFlags.Public, null, new Type[] {typeof(CultureInfo) }, null); var func42 = (Func)Delegate.CreateDelegate(typeof(Func), 42, toStringMethod,true); Console.WriteLine( object.ReferenceEquals(func37.Method,func42.Method)); //true Console.WriteLine(func37.Target);//37 Console.WriteLine(func42.Target);//42 Console.WriteLine(func37(CultureInfo.InvariantCulture));//37 Console.WriteLine(func42(CultureInfo.InvariantCulture));//-201040128... WTF? 

如果实例方法属于值类型(这适用于引用类型),则使用null作为目标对象调用CreateDelegate会引发绑定exception。

几年后的一些后续行动:导致func42(CultureInfo.InvariantCulture);的错误绑定目标func42(CultureInfo.InvariantCulture); 在我的示例中返回"-201040128"而不是"42"是内存损坏,可能允许远程代码执行( cve-2010-1898 ); 这是在2010年ms10-060安全更新中修复的。 当前框架正确打印42! 这并没有使回答这个问题变得更容易,但解释了这个例子中特别奇怪的行为。

你实际上选择了一个特别棘手的例子,原因有两个:

  • ToString()是从objectinheritance但在Int32重写的虚方法。
  • int是一个值类型,当涉及到值类型和实例方法时, Delegate.CreateDelegate()有一些奇怪的规则 – 基本上第一个有效参数变为ref int而不是int

但是,这是String.ToUpper的一个示例,它没有以下任何一个问题:

 using System; using System.Reflection; class Test { static void Main() { MethodInfo method = typeof(string).GetMethod ("ToUpper", BindingFlags.Instance | BindingFlags.Public, null, new Type[]{}, null); Func func = (Func) Delegate.CreateDelegate(typeof(Func), null, method); string x = func("hello"); Console.WriteLine(x); } } 

如果这对你来说足够好,那就太棒了…如果你真的想要int.ToString ,我将不得不尝试一下:)

以下是值类型的示例,使用新的委托类型,该类型通过引用获取其第一个参数:

 using System; using System.Reflection; public struct Foo { readonly string value; public Foo(string value) { this.value = value; } public string DemoMethod() { return value; } } class Test { delegate TResult RefFunc(ref TArg arg); static void Main() { MethodInfo method = typeof(Foo).GetMethod ("DemoMethod", BindingFlags.Instance | BindingFlags.Public, null, new Type[]{}, null); RefFunc func = (RefFunc) Delegate.CreateDelegate(typeof(RefFunc), null, method); Foo y = new Foo("hello"); string x = func(ref y); Console.WriteLine(x); } } 

我不确定,但可能是公开的代表可以帮助你。

更新:如果第一个不起作用,请点击此链接 。

您可以使用Lambdas为您的实例方法获取“有点”编译的静态包装器。

下面的示例并不是非常快,但它应该比任何普通的动态调用快得多。

输出

 100000 iterations took 4 ms 1000000 iterations took 18 ms 10000000 iterations took 184 ms 

代码

 class Program { public sealed class Test { public String Data { get; set; } public override string ToString() { return Data; } } static void Main(string[] args) { TestRun(100000); TestRun(1000000); TestRun(10000000); } private static void TestRun(int iterations) { var toString = typeof(Test).GetMethod("ToString", BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null); var call = GetCall(toString); var tests = (from i in Enumerable.Range(1, iterations) select new Test { Data = "..." + i }).ToList(); var sw = Stopwatch.StartNew(); tests.ForEach(i => call(i)); sw.Stop(); Console.WriteLine("{0} iterations took {1} ms", iterations, sw.ElapsedMilliseconds); } private static Func GetCall(MethodInfo methodInfo) { var input = Expression.Parameter(typeof(T), "input"); MethodCallExpression member = Expression.Call(input, methodInfo); var lambda = Expression.Lambda>(member, input); return lambda.Compile(); } } 

goog方式也许可以在.NET 4.0中使用“动态”类型。 但是Delegate需要实例(对于非静态方法)。 由于多态性等原因,问题在第一时间变得更加复杂……