“非柯里化” .NET 中的实例方法

发布于 2024-07-30 17:54:04 字数 1724 浏览 3 评论 0原文

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

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

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?

如果实例方法属于值类型(这适用于引用类型)。

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

Can you create a delegate of an instance method without specifying the instance at creation time? In other words, can you create a "static" delegate that takes as it's first parameter the instance the method should be called on?

For example, how can I construct the following delegate using reflection?

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

I'm aware of the fact that I can use methodInfo.Invoke, but this is slower, and does not check for type-correctness until it is called.

When you have the MethodInfo of a particular static method, it is possible to construct a delegate using Delegate.CreateDelegate(delegateType, methodInfo), and all parameters of the static method remain free.

As Jon Skeet pointed out, you can simply apply the same to make an open delegate of an instance method if the method is non-virtual on a reference type. Deciding which method to call on a virtual method is tricky, so that's no so trivial, and value-types look like they don't work at all.

For value types, CreateDelegate exhibits really weird behavior:

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?

Calling CreateDelegate with null as the target object throws a binding exception if the instance method belonged to a value type (this works for reference types).

Some follow-up years later: The incorrectly-bound target that caused func42(CultureInfo.InvariantCulture); to return "-201040128" instead of "42" in my example was memory corruption that could have allowed remote code execution (cve-2010-1898); this was fixed in 2010 in the ms10-060 security update. Current frameworks correctly print 42! That doesn't make answering this question any easier, but explains the particularly weird behavior in the example.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

情徒 2024-08-06 17:54:04

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

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

You've actually chosen a particularly tricky example, for two reasons:

  • ToString() is a virtual method inherited from object but overridden in Int32.
  • int is a value type, and there are weird rules with Delegate.CreateDelegate() when it comes to value types and instance methods - basically the first effective parameter becomes ref int rather than int

However, here's an example for String.ToUpper, which doesn't have either of those problems:

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

If that's good enough for you, great... if you really want int.ToString, I'll have to try a bit harder :)

Here's an example for a value type, using a new delegate type which takes its first parameter by reference:

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);
    }
}
夏尔 2024-08-06 17:54:04

我不确定,但可能 开放代表 可以帮你。

更新:请点击此链接(如果是第一个链接)不工作。

I'm not sure, but may be Open delegates can help you.

Upd: Follow this link, if first one don't works.

温柔戏命师 2024-08-06 17:54:04

您可以使用 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();
   }
}

You could use Lambdas to get a "somewhat" compiled static wrapper for your instance method.

The sample below isn't exactly blazingly fast, yet it should be significantly faster than any plain dynamic invoke.

The output

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

The code

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();
   }
}
白云悠悠 2024-08-06 17:54:04

最好的方法可能是使用 .NET 4.0 中的“动态”类型。 但是,委托需要实例(对于非静态方法)。 由于多态性等原因,问题比第一次看起来更复杂......

The goog way maybe can be useing the "dynamic" type in .NET 4.0. However the Delegate need the instance (for non-static methods). The problems is more complex then lokks at first time because of polymorfism etc...

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文