“Uncurrying”使用.NET一个实例方法(“Uncurrying” an instance

2019-06-27 08:55发布

你可以创建一个实例方法的委托,而不在创建时指定的实例? 换句话说,你可以创建一个“静态”的委托,它需要,因为它是第一个参数实例的方法应该叫上?

例如,如何使用反射我构建了以下委托?

Func<int, string> = i=>i.ToString();

我知道的事实,我可以使用methodInfo.Invoke,但这是慢,不检查类型的正确性,直到它被调用。

当你有MethodInfo特定静态方法,可以构建使用委托Delegate.CreateDelegate(delegateType, methodInfo) ,和静态方法的所有参数保持自由。

作为乔恩斯基特指出的那样,你可以简单地将相同的制作实例方法的开放委托,如果该方法是引用类型非虚。 决定在虚拟方法调用哪个方法是棘手的,所以这是没有那么微不足道,和值类型看起来他们没有在所有的工作。

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

var func37 = (Func<CultureInfo,string>)(37.ToString);
var toStringMethod = typeof(int).GetMethod("ToString", BindingFlags.Instance | BindingFlags.Public, null, new Type[] {typeof(CultureInfo) }, null);
var func42 = (Func<CultureInfo,string>)Delegate.CreateDelegate(typeof(Func<CultureInfo,string>), 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?

主叫CreateDelegatenull为目标对象抛出的结合异常,如果实例方法属于一个值类型(这适用于引用类型)。

一些后续年后:导致不正确绑定目标func42(CultureInfo.InvariantCulture); 返回"-201040128"而不是"42"在我的例子是存储器讹误能允许远程执行代码( CVE-2010至1898年 ); 这是在固定在2010 MS10-060安全更新。 当前框架正确打印42! 这不作任何容易回答这个问题,但解释的例子中,特别怪异的行为。

Answer 1:

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

  • 的ToString()是从继承虚拟方法object ,但在覆盖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<string, string> func = (Func<string, string>)
            Delegate.CreateDelegate(typeof(Func<string, string>),
                                    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<TArg, TResult>(ref TArg arg);

    static void Main()
    {
        MethodInfo method = typeof(Foo).GetMethod
            ("DemoMethod", BindingFlags.Instance | BindingFlags.Public,
             null, new Type[]{}, null);
        RefFunc<Foo, string> func = (RefFunc<Foo, string>)
            Delegate.CreateDelegate(typeof(RefFunc<Foo, string>),
                                    null,
                                    method);

        Foo y = new Foo("hello");
        string x = func(ref y);

        Console.WriteLine(x);
    }
}


Answer 2:

我不知道,但可能是开放的代表可以帮助你。

UPD:按照这个链接 ,如果第一次不工作。



Answer 3:

你可以使用Lambda表达式得到您的实例方法“有点”编译静态包装。

下面的示例是不完全的最快的,但它应该比任何纯动态调用显著更快。

输出

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<Test, String>(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<T, M> GetCall<T, M>(MethodInfo methodInfo)
   {
      var input = Expression.Parameter(typeof(T), "input");
      MethodCallExpression member = Expression.Call(input, methodInfo);
      var lambda = Expression.Lambda<Func<T, M>>(member, input);

      return lambda.Compile();
   }
}


Answer 4:

在goog方式也许可以期运用“动态”类型在.NET 4.0中。 然而,代表需要的实例(非静态方法)。 这些问题是比较复杂的,然后在第一次因为polymorfism等lokks ...



文章来源: “Uncurrying” an instance method in .NET