2009-07-31 6 views
5

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

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

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! Это не облегчает ответ на этот вопрос, но объясняет особенно странное поведение в этом примере.

ответ

9

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

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

Это один из тех случаев, когда становится ясно, что C# все еще имеет место для роста в качестве функционального языка. Обработка функций как граждан первого класса по-прежнему остается не такой простой, как нам бы хотелось. Есть ли способ использовать динамические возможности на C# 4, чтобы упростить эту вещь? – LBushkin

+1

@LBushkin: Я так не думаю. На самом деле динамическая типизация и lambdas не идут очень хорошо вместе для начала - компилятор должен знать, какой тип преобразовать лямбда-выражение во время компиляции. –

+0

Я * тестировал на int.ToString, но для фактического использования я полагаю, что мог обойтись без виртуальных методов - хотя и не без структур. Во всяком случае, спасибо за хедз-ап, я упустил сложность виртуальных методов, и сообщение об ошибке не совсем информативно ... –

3

Я не уверен, но может быть Open delegates может вам помочь.

Обновление: Следуйте за этим link, если первый не работает.

+0

, что ссылка ведет на страницу 404 - может быть, вы неправильно набрали? –

+0

Ссылка работает отлично для меня. –

+1

Странно, нажав на ссылку, вы попадаете на страницу 404, обновляя страницу, вы получаете то же самое 404. Но нажатие ввода в строке url (то есть удаление реферирования) вызывает страницу. Некоторая ошибка браузера/сервера, видимо (только FF3.5.1 - хром работает отлично). Во всяком случае, нашел страницу сейчас ;-) –

0

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

2

Вы можете использовать Лямбду, чтобы получить «несколько» скомпилированную статическую обертку для вашего метода экземпляра.

Образец, приведенный ниже, не совсем быстр, но он должен быть значительно быстрее, чем любой обычный динамический вызов.

Выход

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

Это аккуратная идея, спасибо! –

 Смежные вопросы

  • Нет связанных вопросов^_^