2009-07-31 12 views
5

Czy można utworzyć delegata metody instancji bez określania instancji w czasie tworzenia? Innymi słowy, czy możesz utworzyć "statycznego" delegata, który bierze jako swój pierwszy parametr instancję, do której powinna zostać wywołana metoda?"Uncurrying" metoda instancji w .NET

Na przykład, w jaki sposób mogę skonstruować następujące delegate za pomocą refleksji?

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

Jestem świadomy faktu, że mogę wykorzystać methodInfo.Invoke, ale jest wolniejsza i nie sprawdzić poprawność typu, dopóki nie zostanie wywołana.

Kiedy masz MethodInfo konkretnego statycznej metodzie możliwe jest skonstruowanie delegata korzystając Delegate.CreateDelegate(delegateType, methodInfo), a wszystkie parametry statyczne metody pozostają wolne.

Jak zauważył Jon Skeet, można po prostu zastosować to samo, aby utworzyć otwartego delegata metody instancji, jeśli ta metoda nie jest wirtualna dla typu odniesienia. Wybór metody wywołania metody wirtualnej jest trudny, więc nie jest to tak banalne, a typy wartości wyglądają tak, jakby w ogóle nie działały.

Dla typów wartości, CreateDelegate eksponaty naprawdę dziwne zachowanie:

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? 

Wywołanie CreateDelegate z null jako obiekt docelowy rzuca wiążącej wyjątek jeśli metoda wystąpienie należało do typu wartości (to działa dla typów referencyjnych).

Niektóre follow-up lat później: nieprawidłowo związana cel, który spowodował func42(CultureInfo.InvariantCulture); wrócić "-201040128" zamiast "42" w moim przykładzie było uszkodzenia pamięci, który mógł pozwolić na zdalne wykonanie kodu (cve-2010-1898); zostało to naprawione w 2010 roku w aktualizacji zabezpieczeń ms10-060. Obecne frameworki poprawnie drukują 42! To nie ułatwia odpowiedzi na to pytanie, ale wyjaśnia szczególnie dziwne zachowanie w tym przykładzie.

Odpowiedz

9

pan właściwie dobrane szczególnie trudne przykład, z dwóch powodów:

  • toString() jest metodą wirtualną odziedziczony object, ale zastąpiono je w Int32.
  • int jest typ wartości, a są dziwne zasady z Delegate.CreateDelegate() jeśli chodzi o typy wartości i metody instancji - w zasadzie pierwszy skuteczny parametr staje ref int zamiast int

Jednak oto przykład dla String.ToUpper, który nie posiada żadnego z tych problemów:

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

Jeśli to wystarczająco dobre dla ciebie, wielki ... jeśli naprawdę chcesz int.ToString, będę musiał spróbować nieco trudniejsze :)

Oto przykład dla typu wartości, wykorzystując nowy typ delegata, który zaczyna swój pierwszy parametr przez odniesienie:

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

Jest to jeden z tych przypadków, w których staje się jasne, że C# nadal ma miejsce na rozwój jako język funkcjonalny. Traktowanie funkcji jako pierwszorzędnych obywateli nadal nie jest tak płynne, jak byśmy chcieli. Czy jest jakiś sposób na wykorzystanie dynamicznych funkcji w C# 4, aby tego rodzaju rzeczy były łatwiejsze? – LBushkin

+1

@LBushkin: Nie sądzę. W rzeczywistości, dynamiczne pisanie i lambdas nie układają się ze sobą zbyt dobrze - kompilator musi wiedzieć, jaki typ konwertować wyrażenie lambda na czas kompilacji. –

+0

Ja * miałem * testowałem na int.ToString, ale do rzeczywistego użycia przypuszczam, że mógłbym obejść się bez wirtualnych metod - choć nie bez struktur. W każdym razie, dzięki za heads-up, przeoczyłem złożoność metod wirtualnych, a komunikat o błędzie nie jest dokładnie informacyjny ... –

3

Nie jestem pewien, ale może być Open delegates może pomóc.

Aktualizacja: Postępuj zgodnie z link, jeśli pierwszy nie działa.

+0

Ten link prowadzi do strony 404 - być może literówka? –

+0

Link działa dobrze dla mnie. –

+1

Dziwne, klikając na link prowadzi mnie do strony 404, odświeżanie strony wywołuje to samo 404 - Ale naciśnięcie klawisza Enter na pasku adresu, a następnie (usunięcie referer) powoduje wyświetlenie strony. Niektóre błędy przeglądarki/serwera, widocznie (tylko FF3.5.1 - chrome działa bez zarzutu). W każdym razie znalazłem stronę ;-) –

0

Sposób na to, że może być używany typ "dynamiczny" w .NET 4.0. Jednak Delegat potrzebuje instancji (dla metod niestatycznych). Problemów jest bardziej skomplikowany wtedy lokks na raz pierwszy z powodu polymorfism etc ...

2

można użyć lambdas aby uzyskać „nieco” skompilowany statycznych otoki dla metody instancji.

Poniższy przykład nie jest dokładnie niesamowicie szybki, ale powinien być znacznie szybszy niż zwykłe dynamiczne wywoływanie.

Wyjście

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

Kod

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

To zgrabny pomysł, dzięki! –

Powiązane problemy