代码之家  ›  专栏  ›  技术社区  ›  Eamon Nerbonne

“uncurrying”是.NET中的一个实例方法

  •  5
  • Eamon Nerbonne  · 技术社区  · 15 年前

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

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

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

    我知道我可以使用MethodInfo.Invoke,但是这样做比较慢,并且在调用它之前不会检查类型的正确性。

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

    如jon skeet所指出的,如果实例方法在引用类型上是非虚拟的,则可以简单地应用该方法来生成实例方法的开放委托。决定调用虚拟方法的方法是很困难的,所以这并不简单,而且值类型看起来根本不起作用。

    对于值类型, 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?
    

    打电话 创造优雅 具有 null 如果实例方法属于值类型(这适用于引用类型),则目标对象将引发绑定异常。

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

    4 回复  |  直到 7 年前
        1
  •  9
  •   Robert Harvey    7 年前

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

    • 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);
        }
    }
    
        2
  •  3
  •   Alex Kofman    15 年前

    我不确定,但可能是 Open delegates 可以帮助你。

    upd:按此操作 link 如果第一个不起作用。

        3
  •  2
  •   Robert Giesecke    15 年前

    可以使用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();
       }
    }
    
        4
  •  0
  •   TcKs    15 年前

    goog方法可能可以使用.NET 4.0中的“动态”类型。但是委托需要实例(对于非静态方法)。由于多元论等原因,这些问题比洛克斯问题更为复杂。