2015-01-28 9 views
5

Ostatnio chciałem dodać opcjonalny parametr do metody rozszerzenia. Oryginalna metoda wyglądała następująco:Metoda C# z domyślną wartością parametru nie generuje przeciążenia bez parametru?

public static class Extensions { 
    public static bool Foo(this IFoo target) { 
     target.DoIt(true); 
    } 
} 

Jest to oczywiście wersja uproszczona, ale odejdźmy od tego.

Co zrobiłem to:

public static class Extensions { 
    public static bool Foo(this IFoo target, bool flag = true) { 
     target.DoIt(flag); 
    } 
} 

Wszystko zrobiłem jest wprowadzenie opcjonalnego parametru z wartością domyślną, prawda? Spodziewałem się kompilatora generującego przeciążoną metodę bez flagi. To się częściowo zdarzyło. Każdy kod że zrekompilowane mógł skompilować i wykonać bez żadnych problemów, nawet bez parametru jak ten:

... 
IFoo foo = new FooBar(); 
foo.Foo(); 
... 

jednak żadnego kodu, który został zbudowany na poprzedniej wersji Foo() nie działa, rzucając następujący wyjątek:

Unhandled Exception: System.MissingMethodException: Method not found: 'Boolean Models.Class1.Foo()'. 
at DefaultParamsTests.Program.Main(String[] args) 

jest to oczywiście problem dla nas, jak mamy API publiczną, że nasi klienci zrobić dźwigni i byłoby to łamanie zmiana.

Rozwiązaniem jest jawnie utworzyć przeciążeniem:

public static class Extensions { 
    public static bool Foo(this IFoo target) { 
     target.DoIt(true); 
    } 

    public static bool Foo(this IFoo target, bool) { 
     target.DoIt(true); 
    } 
} 

Jednak Resharper sugeruje, że mogę wprowadzić opcjonalny parametr dla metody foo.

resharper introduce optional parameter

Jeśli będę postępować zgodnie z refactoring to w zasadzie robi to, co pokazałem powyżej. To jednak nie zadziała w przypadku istniejącego kodu.

resharper refactored

I spojrzał na wygenerowanym przy użyciu zarówno IL reflektor i dotPeek. Żadne nie pokazuje generacji przeciążenia.

Czego mi brakuje?

+0

Co powoduje ci wierzyć, że kompilator będzie generować dwa przeciążenia metody dla pojedynczej deklaracji? – gknicker

+1

Myślę, że już odpowiedziałeś na swoje pytanie. Czy możesz po prostu dodać tę metodę i zignorować sugestię Resharpera, aby wprowadzić opcjonalny parametr? – maxwellb

+0

@gknicker, oczywiście, gdzieś powstaje przeciążenie. W przeciwnym razie parametry domyślne nie działałyby. Jak wskazały dwie osoby, które odpowiedziały na to pytanie, zakładałem, że zostało wygenerowane w niewłaściwym miejscu. Wartość domyślna jest wstrzykiwana przez kompilator w miejscu wywołania zamiast w miejscu deklaracji. –

Odpowiedz

4

W przypadku parametrów domyślnych faktyczna strona z ogłoszeniami jest ponownie zapisywana, aby użyć wartości domyślnej. Oznacza to, że przeciążenie nie jest generowane, kod wywołujący jest modyfikowany!

public void fun(int x = 3) { } 

// in another file 
fun(); // compiler re-writes to fun(3); 
fun(7); // fun(7) as expected 

// actual code generated by the c# compiler 
fun(3); 
fun(7); 

Jeśli używasz opcjonalnych parametrów w C# trzeba ponownie skompilować wszystkie rozmówców gdy function zmiany. Z tego powodu jest on zniechęcony do umieszczania tego rodzaju metod na publicznym interfejsie (na przykład wywoływanym przez kod innego użytkownika). Używanie ich w swoich połączonych projektach jest w porządku, ponieważ wszystkie one powinny się kompilować w tym samym czasie.

+1

Innym problemem jest to, że nie można wiarygodnie "zaktualizować domyślnej wartości". Każdy kod skompilowany, gdy domyślna wartość to "foo", będzie nadal wywoływał metodę z "foo". Aktualizacja wartości domyślnej do "bar" zostanie wywołana tylko jako nowy kod zbudowany z tą definicją. – maxwellb

+1

@maxwellb, to ten sam problem. Kompilator ponownie napisał wezwanie dla ciebie, więc musisz ponownie skompilować. Jeśli jest to kod wewnętrzny, używany tylko wewnątrz twoich projektów (i ma odpowiednie zależności), to w niektórych przypadkach jest to dobry sposób na zaoszczędzenie czasu. Zawsze używam przeciążeń poza "wewnętrznymi" klasami, na wypadek gdyby ktoś nadużywał tego, co napisałem: D –

+1

dokładnie. Zamierzałem dodać do twojej odpowiedzi kolejny przykład, dlaczego opcjonalne parametry mają "gotchas" i powinny być używane oszczędnie. – maxwellb

4

Parametry opcjonalne w metodach w języku C# są rozwiązywane podczas kompilacji w witrynie wywołania. Twój kod wywołujący, który pomija parametr, rozszerza się, dołączając parametr, wywołując wersję z dwoma parametrami. Jest to przeciwieństwo tego, co przypuszczasz, z powodu nadmiernego obciążenia.

1

Kluczem jest to, że kompilator zmienia osoby dzwoniącej metody, która ma zawierać wartość domyślną.

Zobacz nowy kod w dotPeek.

UWAGA: Powinieneś usunąć pliki pdb, gdy używasz dotPeek we własnych plikach!

Oto klasa rozszerzeń z dwiema metodami (dekompilowana za pomocą dotPeek). Jedna z ustawieniem domyślnym i jedna bez tej wartości nieobsługiwana. Wszystko wygląda dobrze. Teraz spójrz na obiekt wywołujący metodę rozszerzenia (dekompilowany za pomocą dotPeek).

Należy zauważyć, że osoba wywołująca metodę rozszerzenia IFoo2 faktycznie zawiera wartość false dla metody Foo rozszerzeń klasy. Tak więc twoja metoda Extensons.Foo ma teraz dwa parametry. Pierwsza metoda obiektu, a druga jest parametrem "domyślnym". Tak więc każdy kod utworzony za pomocą pierwszej wersji zakończy się niepowodzeniem, ponieważ nie ma metody z tylko jednym parametrem.

Wyłącz resharper wokół metod i nie będzie widać, że cię już prześladować :)

Powiązane problemy