2009-06-19 11 views
28

rozszerzenie metod może być przypisana do delegatów pasujących do ich wykorzystania na obiekcie, tak:Metody rozszerzeń zdefiniowane dla typów wartości nie mogą być używane do tworzenia delegatów - dlaczego nie?

static class FunnyExtension { 
    public static string Double(this string str) { return str + str; } 
    public static int Double(this int num) { return num + num; } 
} 


Func<string> aaMaker = "a".Double; 
Func<string, string> doubler = FunnyExtension.Double; 

Console.WriteLine(aaMaker());  //Prints "aa" 
Console.WriteLine(doubler("b")); //Prints "bb" 

Jeśli typ oni rozszerzenie jest typ wartości, to nie będzie działać:

Func<int> eightMaker = 4.Double; //Error CS1113: Extension methods 'FunnyExtension.Double(int)' defined on value type 'int' cannot be used to create delegates 
Func<int, int> intDoubler = FunnyExtension.Double; //Works 

daje

Błąd CS1113: 'FunnyExtension.Double (int)' metody rozszerzenie zdefiniowane na wartości typu 'int' nie może być wykorzystywane do tworzenia delegatów.

Dlaczego oni nie mogą?

+0

Czy na pewno nie jest to CS1113? –

+0

Jest; naprawiony. Dzięki – SLaks

Odpowiedz

18

w odpowiedzi na inne moją odpowiedź, Eric Smith prawidłowo zauważa:

"... bo wymagałoby to niejawnie boks parametr typu odbiornik ...". I tak się dzieje, jeśli zrobisz coś takiego: Func f = 5.ToString; Co jest całkowicie legalne.

Myślenie o tym doprowadziło mnie do nowej odpowiedzi. Wypróbuj to dla rozmiaru:

Zwykłe metody "instancji" na strukturach pobierają, na poziomie CIL, "zarządzany wskaźnik" (typ &) jako parametr odbiornika. Jest to konieczne, aby metody instancji w strukturach mogły przypisać do pól struktury. Zobacz Partition II, Section 13.3.

Podobnie, metody instancji na klasach przyjmują "odwołanie do obiektu" (typ O) jako parametr odbiornika (różnica polega na tym, że jest to wskaźnik do zarządzanej sterty i musi być śledzone dla GC).

Ponieważ zarówno CIL & jak i O s mogą być (i są) implementowane przez wskaźniki, wszystko jest w porządku do implementacji delegatów. Bez względu na to, czy delegat przechwytuje metodę statyczną, metodę instancji klasy lub metodę instancji struct, wystarczy przekazać wskaźnik do jej _target do pierwszego argumentu funkcji.

Ale scenariusz, który omawiamy, niszczy to. Metoda statycznego rozszerzania przyjmująca jako argument int wymaga argumentu CIL typu int32 (patrz rozdział III, sekcja 1.1.1). Oto, co dzieje się z szynami. Nie widzę żadnego powodu, dla którego nie byłby to możliwy do realizacji delegatów, aby zdać sobie sprawę, że tak się dzieje (na przykład, poprzez sprawdzenie metadanych związanych z przechwytywanym MethodInfo) i emitować thunk, który byłby unbox _target i przekazanie tego jako pierwszego argumentu, ale , nie jest potrzebne dla delegatów do klasycznych metod instancji na strukturach, ponieważ oczekują one wskaźnika i tak nie są i nie pojawiają się (sądząc po przykładzie w mojej wcześniejszej niepoprawnej odpowiedzi) do uprawomocnić się. Oczywiście dany konkretny rodzaj wartości kontrolowałby dokładny charakter wymaganego thunk.

O ile nie brakuje mi bardziej fundamentalnej przeszkody we wdrażaniu (mogę sobie wyobrazić, że na przykład może to stanowić problem dla weryfikatora), wydaje się, że można rozsądnie uzasadnić rozszerzenie środowiska wykonawczego w celu wsparcia tej sprawy, ale wszystkie znaki wskazują na to, że jest to ograniczenie środowiska wykonawczego, a nie kompilatora C# jako takiego.

+5

Doug, your Analiza jest doskonała. Kiedy rozmawialiśmy przez e-mail, mój kolega Sereekar zrobił kilka notatek na swoim blogu http://blogs.msdn.com/sreekarc/archive/2009/06/25/why-can-t-extension-methods-on-value- type-be-curried.aspx i napisałem kilka uwag o tym, jak ten problem pojawia się z "Curry'ego refleksji" na moim blogu. http://blogs.msdn.com/ericlippert/archive/2009/06/25/mmm-curry.aspx –

+1

Ogromne podziękowania dla Erica i Sreekara za poświęcenie czasu na zapoznanie się z tym i wyjaśnienie tego wszystkim! –

+1

Zobacz także wpis na blogu [Open Delegates vs. Closed Delegates] (http://blog.slaks.net/2011/06/open-delegates-vs-closed-delegates.html), który został napisany przez pytającego (S. Laks) w późniejszym czasie. –

2

EDIT 2 Nie wierzę już tej odpowiedzi, ale zostawiłem go tutaj tak nitka wciąż sensu i tak, że ludzie, dlaczego to nie jest w porządku. Zobacz moją drugą odpowiedź na inny temat w tej sprawie.

Original

Ponieważ wymagałoby to niejawnie boks wartość parametru typ odbiornika (ponieważ pole _target w rodzaju System.Delegate która posiada odbiornik parametr jest typu System.Object), co może prowadzić do jakiegoś dziwnego zachowania, jeśli nie oczekiwałeś tego.

EDIT

Jest jeszcze coś się tutaj dzieje. Uruchomiłem ten przykładowy program:

class Program 
{ 
    public static int Combine(int a, int b) 
    { 
     return a + b; 
    } 

    static void Main(string[] args) 
    { 
     var combineMethod = typeof(Program).GetMethod("Combine"); 
     var add4 = Delegate.CreateDelegate(
           typeof(Converter<int, int>), 
           4, 
           combineMethod) as Converter<int, int>; 

     for (int i = 0; i < 10; i++) 
     { 
      Console.WriteLine(add4(i)); 
     } 
     Console.ReadLine(); 
    } 
} 

i otrzymałem ArgumentException: "Błąd wiążący się z metodą docelową." na wezwanie do CreateDelegate. Nie jestem pewien dlaczego, a ponieważ odpowiednia metoda jest metoda internalcall, Reflector nie jest zbyt pomocne. documentation for CreateDelegate również nie było zbyt pomocne. Jestem pewien, że ma to coś wspólnego z boksowaniem odbiornika, może ktoś z wiedzą o źródle Rotora może pomóc wyjaśnić dlaczego?

+0

Jakie dziwne zachowania aliasów? –

+0

Jakie zachowanie aliasów może prowadzić do tego, czego byś się nie spodziewał? Ponieważ metoda rozszerzenia przyjmuje już typ wartości jako zwykły parametr, nie ma powodu, aby zakładać, że nie wykonałaby kopii. – SLaks

+0

Jeśli zadzwonisz do delegata dwa razy, będzie to na tej samej pudełkowej kopii struktury. Nie jestem pewien, czy to się kwalifikuje jako nieoczekiwane. –