«Неудачный» метод экземпляра в .NET - PullRequest
5 голосов
/ 31 июля 2009

Можете ли вы создать делегат метода экземпляра без указания экземпляра во время создания? Другими словами, можете ли вы создать «статический» делегат, который принимает в качестве первого параметра экземпляр, к которому должен вызываться метод?

Например, как я могу создать следующий делегат, используя отражение?

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?

Вызов CreateDelegate с null в качестве целевого объекта вызывает исключение привязки, если метод экземпляра принадлежит типу значения (это работает для ссылочных типов).

Несколько последующих лет спустя: Неправильно связанная цель, из-за которой func42(CultureInfo.InvariantCulture); вернула "-201040128" вместо "42" в моем примере, была повреждением памяти, которое могло позволить удаленное выполнение кода ( CVE-2010-1898 ); это было исправлено в 2010 году в обновлении безопасности ms10-060 . Текущие рамки правильно печатать 42! Это не облегчает ответ на этот вопрос, но объясняет особенно странное поведение в примере.

Ответы [ 4 ]

9 голосов
/ 31 июля 2009

Вы на самом деле выбрали особенно хитрый пример по двум причинам:

  • 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);
    }
}
3 голосов
/ 31 июля 2009

Я не уверен, но может быть Открытые делегаты может помочь вам.

Upd: перейдите по этой ссылке , если первая не работает.

2 голосов
/ 31 июля 2009

Вы можете использовать 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<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();
   }
}
0 голосов
/ 31 июля 2009

Возможно, способом goog является использование «динамического» типа в .NET 4.0. Однако делегату нужен экземпляр (для нестатических методов). Проблемы сложнее, чем в первый раз, из-за полиморфизма и т. Д. ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...