Jakie są różnice we wdrażaniu interfejsów niejawnie i jawnie w języku C #?
Kiedy należy używać niejawnego, a kiedy jawnego?
Czy są jakieś zalety i / lub wady jednego lub drugiego?
Oficjalne wytyczne Microsoft (z pierwszej edycji Framework Design Guidelines ) stwierdzają, że użycie jawnych implementacji nie jest zalecane , ponieważ powoduje to nieoczekiwane zachowanie kodu.
Myślę, że ta wytyczna jest bardzo ważna w czasach sprzed IoC , kiedy nie podajesz rzeczy jako interfejsów.
Czy ktoś mógłby również dotknąć tego aspektu?
Odpowiedzi:
Implikowane jest to, gdy definiujesz interfejs za pośrednictwem członka swojej klasy. Jawne jest, gdy definiujesz metody w swojej klasie na interfejsie. Wiem, że to brzmi myląco, ale oto, co mam na myśli:
IList.CopyTo
zostanie domyślnie zaimplementowane jako:i wyraźnie jako:
Różnica polega na tym, że niejawna implementacja umożliwia dostęp do interfejsu poprzez klasę utworzoną przez rzutowanie interfejsu jako tej klasy i jako samego interfejsu. Jawna implementacja umożliwia dostęp do interfejsu tylko poprzez rzutowanie go jako samego interfejsu.
Używam jawnego przede wszystkim do utrzymania implementacji w czystości lub gdy potrzebuję dwóch implementacji. Niezależnie od tego rzadko go używam.
Jestem pewien, że istnieje więcej powodów, aby używać / nie używać wyraźnych treści, które inni opublikują.
Zobacz następny post w tym wątku, aby uzyskać doskonałe uzasadnienie każdego z nich.
źródło
public
słowo kluczowe ... w przeciwnym razie wystąpi błądUISwitch IScoreRegPlayerViewCell.markerSwitch { get { return markerSwitch; } }
.IEnumerator<T>.Current
,IEnumerable<T>.GetEnumerator()
iISet<T>.Add(T)
. Jest to wspomniane w innej odpowiedzi .Niejawną definicją byłoby po prostu dodanie metod / właściwości itp. Wymaganych przez interfejs bezpośrednio do klasy jako metod publicznych.
Jawna definicja zmusza członków do ujawnienia się tylko podczas bezpośredniej pracy z interfejsem, a nie podstawowej implementacji. Jest to preferowane w większości przypadków.
źródło
Cat
rzeczywiścieIEatable
nie ma zastrzeżeń.Oprócz dostarczonych już doskonałych odpowiedzi, w niektórych przypadkach WYMAGANA jest jawna implementacja, aby kompilator mógł dowiedzieć się, co jest wymagane. Spójrz na
IEnumerable<T>
przykład, który prawdopodobnie będzie pojawiał się dość często.Oto przykład:
Tutaj
IEnumerable<string>
implementujeIEnumerable
, dlatego też musimy. Ale poczekaj, zarówno wersja ogólna, jak i normalna, oba implementują funkcje z tą samą sygnaturą metody (C # ignoruje dla tego typu zwrot). Jest to całkowicie legalne i w porządku. Jak kompilator rozpoznaje, którego użyć? Zmusza cię do posiadania co najwyżej jednej niejawnej definicji, a następnie może rozwiązać wszystko, czego potrzebuje.to znaczy.
PS: Mały fragment pośredni w jawnej definicji IEnumerable działa, ponieważ w funkcji kompilator wie, że faktycznym typem zmiennej jest StringList, i tak rozwiązuje wywołanie funkcji. Wygląda na to, że udało się zgromadzić sprytny fakt dotyczący implementacji niektórych warstw abstrakcji w niektórych interfejsach rdzenia platformy .NET.
źródło
abstract
.Przytoczyć Jeffrey Richter z CLR pośrednictwem C #
( EIMI oznacza E Xplicit I nterface M ethod I DROŻENIE)
Jeśli używasz odwołania do interfejsu DOWOLNY łańcuch wirtualny można jawnie zastąpić EIMI w dowolnej klasie pochodnej, a gdy obiekt tego typu zostanie rzutowany na interfejs, łańcuch wirtualny zostanie zignorowany i wywołana zostanie jawna implementacja. To nic innego jak polimorfizm.
EIMI można także wykorzystać do ukrycia elementów interfejsu o słabym typie przed implementacjami podstawowych interfejsów Framework, takich jak IEnumerable <T>, aby klasa nie ujawniała bezpośrednio metody o słabym typie, ale jest poprawna pod względem składniowym.
źródło
Powód nr 1
Zazwyczaj używam jawnej implementacji interfejsu, gdy chcę zniechęcić do „programowania do implementacji” ( Zasady projektowania z wzorców projektowych ).
Na przykład w aplikacji internetowej opartej na MVP :
Teraz inna klasa (na przykład prezenter ) ma mniejsze szanse na zależność od implementacji StandardNavigator, a bardziej na interfejs INavigator (ponieważ implementacja musiałaby zostać przekazana do interfejsu, aby skorzystać z metody przekierowania).
Powód nr 2
Innym powodem, dla którego mógłbym zastosować jawną implementację interfejsu, byłoby utrzymanie „domyślnego” interfejsu klasy w czystości. Na przykład, gdy opracowuję kontrolkę serwera ASP.NET , mogę chcieć mieć dwa interfejsy:
Oto prosty przykład. Jest to kontrolka pola kombi, która wyświetla listę klientów. W tym przykładzie twórca strony internetowej nie jest zainteresowany zapełnianiem listy; zamiast tego chcą po prostu móc wybrać klienta według GUID lub uzyskać identyfikator GUID wybranego klienta. Prezenter zapełni pole przy ładowaniu pierwszej strony, a prezenter jest hermetyzowany przez kontrolkę.
Prezenter wypełnia źródło danych, a twórca strony internetowej nigdy nie musi być świadomy jego istnienia.
Ale to nie jest srebrna kula armatnia
Nie polecałbym zawsze stosowania jawnych implementacji interfejsu. To tylko dwa przykłady, w których mogą być pomocne.
źródło
Oprócz innych podanych już powodów, jest to sytuacja, w której klasa implementuje dwa różne interfejsy, które mają właściwość / metodę o tej samej nazwie i sygnaturze.
Ten kod kompiluje się i działa poprawnie, ale właściwość Title jest wspólna.
Oczywiście chcielibyśmy, aby wartość tytułu była zależna od tego, czy traktujemy Class1 jak książkę czy osobę. Właśnie wtedy możemy użyć jawnego interfejsu.
Zauważ, że jawne definicje interfejsów są wywnioskowane jako publiczne - i dlatego nie można ich jawnie (lub inaczej) jawnie określać.
Zauważ też, że nadal możesz mieć wersję „współdzieloną” (jak pokazano powyżej), ale chociaż jest to możliwe, istnienie takiej właściwości jest wątpliwe. Być może można go użyć jako domyślnej implementacji tytułu - aby istniejący kod nie musiał być modyfikowany, aby rzutować Class1 na IBook lub IPerson.
Jeśli nie zdefiniujesz „udostępnionego” (niejawnego) tytułu, konsumenci klasy 1 muszą jawnie przesłać instancje klasy 1 najpierw do IBook lub IPerson - w przeciwnym razie kod nie zostanie skompilowany.
źródło
Najczęściej używam jawnego wdrożenia interfejsu. Oto główne powody.
Refaktoryzacja jest bezpieczniejsza
Podczas zmiany interfejsu lepiej jest, jeśli kompilator może go sprawdzić. Jest to trudniejsze w przypadku implementacji niejawnych.
Przychodzą mi na myśl dwa typowe przypadki:
Dodanie funkcji do interfejsu, w którym istniejąca klasa, która implementuje ten interfejs, ma już metodę z taką samą sygnaturą jak nowa . Może to prowadzić do nieoczekiwanego zachowania i kilka razy mnie ugryzł. Trudno jest „zobaczyć” podczas debugowania, ponieważ funkcja ta prawdopodobnie nie znajduje się przy innych metodach interfejsu w pliku (problem z samodokumentowaniem wymieniony poniżej).
Usuwanie funkcji z interfejsu . Metody niejawnie zaimplementowane będą nagle martwym kodem, ale metody jawnie zaimplementowane zostaną złapane przez błąd kompilacji. Nawet jeśli martwy kod dobrze jest zachować, chcę zostać zmuszony do jego przeglądu i promocji.
Szkoda, że C # nie ma słowa kluczowego, które zmusza nas do oznaczenia metody jako implementacji niejawnej, więc kompilator może wykonać dodatkowe kontrole. W metodach wirtualnych nie występuje żaden z powyższych problemów z powodu wymaganego użycia „zastępowania” i „nowego”.
Uwaga: w przypadku stałych lub rzadko zmieniających się interfejsów (zazwyczaj z interfejsów API dostawców) nie stanowi to problemu. Jednak w przypadku moich własnych interfejsów nie mogę przewidzieć, kiedy / jak się zmienią.
To jest samodokumentowanie
Jeśli zobaczę „public bool Execute ()” w klasie, zajmie to dodatkową pracę, aby dowiedzieć się, że jest to część interfejsu. Ktoś prawdopodobnie będzie musiał to skomentować, mówiąc o tym, lub umieścić go w grupie innych implementacji interfejsu, wszystko pod regionem lub komentarzem grupującym z napisem „implementacja ITask”. Oczywiście działa to tylko wtedy, gdy nagłówek grupy nie jest poza ekranem.
Natomiast: „bool ITask.Execute ()” jest jasne i jednoznaczne.
Wyraźne oddzielenie implementacji interfejsu
Myślę, że interfejsy są bardziej „publiczne” niż metody publiczne, ponieważ są one zaprojektowane tak, aby odsłonić tylko odrobinę powierzchni określonego typu betonu. Redukują typ do możliwości, zachowania, zestawu cech itp. A we wdrażaniu myślę, że warto zachować tę separację.
Kiedy patrzę na kod klasy, kiedy natrafiam na jawne implementacje interfejsu, mój mózg przechodzi w tryb „kontraktowania kodu”. Często te implementacje po prostu przekazują inne metody, ale czasami będą przeprowadzać dodatkowe sprawdzanie stanu / parametrów, konwersję przychodzących parametrów w celu lepszego dopasowania do wymagań wewnętrznych, a nawet tłumaczenie dla celów wersjonowania (tj. Wiele generacji interfejsów sprowadzających się do typowych implementacji).
(Zdaję sobie sprawę, że publikacje publiczne są również kontraktami kodowymi, ale interfejsy są znacznie silniejsze, szczególnie w bazie kodu opartej na interfejsie, w której bezpośrednie użycie konkretnych typów jest zwykle oznaką kodu wyłącznie wewnętrznego).
Powiązane: Powód 2 powyżej autorstwa Jona .
I tak dalej
Plus zalety wspomniane już w innych odpowiedziach tutaj:
Problemy
To nie tylko zabawa i szczęście. W niektórych przypadkach trzymam się implicitów:
Ponadto rzutowanie może być uciążliwe, gdy w rzeczywistości masz konkretny typ i chcesz wywołać jawną metodę interfejsu. Radzę sobie z tym na jeden z dwóch sposobów:
public IMyInterface I { get { return this; } }
(należy wstawić) i wywołajfoo.I.InterfaceMethod()
. Jeśli wiele interfejsów wymaga tej umiejętności, rozwiń nazwę poza ja (z mojego doświadczenia wynika, że mam taką potrzebę).źródło
Jeśli zaimplementujesz jawnie, będziesz mógł odwoływać się do elementów interfejsu tylko poprzez odwołanie, które jest typu interfejsu. Odwołanie, które jest rodzajem klasy implementującej, nie ujawni tych elementów interfejsu.
Jeśli twoja klasa implementująca nie jest publiczna, z wyjątkiem metody użytej do utworzenia klasy (która może być kontenerem fabrycznym lub IoC ) i oprócz metod interfejsu (oczywiście), nie widzę żadnej korzyści z jawnej implementacji interfejsy.
W przeciwnym razie jawne implementowanie interfejsów gwarantuje, że odwołania do konkretnej klasy implementacyjnej nie będą używane, co pozwoli na zmianę tej implementacji w późniejszym czasie. „Zapewnia”, jak sądzę, jest „zaletą”. Dobrze przemyślana implementacja może to osiągnąć bez wyraźnej implementacji.
Wadą jest, moim zdaniem, to, że w kodzie implementacji, który ma dostęp do niepublicznych członków, znajdziesz typy przesyłania do / z interfejsu.
Jak wiele rzeczy, zaletą jest wada (i odwrotnie). Jawne implementowanie interfejsów zapewni, że kod implementacyjny konkretnej klasy nie zostanie ujawniony.
źródło
Implementacja interfejsu niejawnego polega na zastosowaniu metody o tej samej sygnaturze interfejsu.
Implementacja interfejsu jawnego polega na jawnym zadeklarowaniu interfejsu, do którego należy metoda.
MSDN: niejawne i jawne implementacje interfejsu
źródło
Każdy członek klasy, który implementuje interfejs, eksportuje deklarację semantycznie podobną do sposobu, w jaki zapisywane są deklaracje interfejsu VB.NET, np.
Chociaż nazwa członka klasy jest często taka sama, jak nazwa członka interfejsu, a członek klasy często jest publiczny, żadna z tych rzeczy nie jest wymagana. Można również zadeklarować:
W takim przypadku klasa i jej pochodne miałyby dostęp do członka klasy przy użyciu nazwy
IFoo_Foo
, ale świat zewnętrzny byłby w stanie uzyskać dostęp do tego konkretnego członka tylko poprzez przesłanie doIFoo
. Takie podejście jest często dobre w przypadkach, w których metoda interfejsu ma określone zachowanie we wszystkich implementacjach, ale użyteczne zachowanie tylko w niektórych [np. Określone zachowanie dlaIList<T>.Add
metody kolekcji tylko do odczytu to rzutowanieNotSupportedException
]. Niestety jedynym prawidłowym sposobem implementacji interfejsu w języku C # jest:Nie tak miło.
źródło
Jednym z ważnych zastosowań implementacji interfejsu jawnego jest konieczność implementacji interfejsów o mieszanej widoczności .
Problem i rozwiązanie zostały dobrze wyjaśnione w artykule C # Internal Interface .
Na przykład, jeśli chcesz zabezpieczyć wyciek obiektów między warstwami aplikacji, ta technika pozwala określić inną widoczność elementów, które mogą powodować wyciek.
źródło
Poprzednie odpowiedzi wyjaśniają, dlaczego implementacja interfejsu w języku C # może być preferowana (głównie z powodów formalnych). Jest jednak jedna sytuacja, w której jawna implementacja jest obowiązkowa : Aby uniknąć wycieku z enkapsulacji, gdy interfejs nie jest
public
, ale klasa implementująca jestpublic
.Powyższy wyciek jest nieunikniony, ponieważ zgodnie ze specyfikacją C # „Wszyscy członkowie interfejsu domyślnie mają dostęp publiczny”. W konsekwencji implementacje niejawne muszą również zapewniać
public
dostęp, nawet jeśli sam interfejs to npinternal
.Implementacja interfejsu niejawnego w języku C # to wielka wygoda. W praktyce wielu programistów używa go cały czas / wszędzie bez dalszego rozważania. Prowadzi to w najlepszym przypadku do powstawania nieuporządkowanych powierzchni, aw najgorszym do nieszczelności. Inne języki, takie jak F #, nawet na to nie pozwalają .
źródło