Interfejsy C #. Implementacja niejawna a implementacja jawna


632

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?


Przeczytaj cały artykuł na temat interfejsów C #: planetofcoders.com/c-interfaces
Gaurav Agrawal

Tak, należy unikać wyraźnych interfejsów, a bardziej profesjonalne podejście do wdrożenia ISP (zasada segregacji interfejsów) tutaj jest szczegółowym artykułem na temat tego samego codeproject.com/Articles/1000374/...
Shivprasad Koirala

Odpowiedzi:


492

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.CopyTozostanie domyślnie zaimplementowane jako:

public void CopyTo(Array array, int index)
{
    throw new NotImplementedException();
}

i wyraźnie jako:

void ICollection.CopyTo(Array array, int index)
{
    throw new NotImplementedException();
}

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.

MyClass myClass = new MyClass(); // Declared as concrete class
myclass.CopyTo //invalid with explicit
((IList)myClass).CopyTo //valid with explicit.

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.


8
Wiem, że ten post jest stary, ale uważam, że jest bardzo przydatny - jedną rzeczą jest zauważyć, jeśli nie jest jasne, ponieważ nie było dla mnie to, że w przykładzie niejawne ma publicsłowo kluczowe ... w przeciwnym razie wystąpi błąd
jharr100,

CLR Jeffreya Richtera za pośrednictwem C # 4 ed ch 13 pokazuje przykład, w którym rzutowanie nie jest potrzebne: wewnętrzna struktura SomeValueType: IComparable {private Int32 m_x; public SomeValueType (Int32 x) {m_x = x; } public Int32 CompareTo (SomeValueType other) {...);} Int32 IComparable.CompareTo (Object other) {return CompareTo ((SomeValueType) other); }} public static void Main () {SomeValueType v = new SomeValueType (0); Obiekt o = nowy Object (); Int32 n = v.CompareTo (v); // Brak boksu n = v.CompareTo (o); // błąd czasu kompilacji}
Andy Dent

1
Dzisiaj spotkałem rzadką sytuację, która WYMAGAŁA użycia jawnego interfejsu: Klasa z polem wygenerowanym przez konstruktora interfejsu, który tworzy pole jako prywatne (Xamarin celuje w iOS, używając storyboardu iOS). Oraz interfejs, w którym sensowne było ujawnienie tego pola (tylko do odczytu publicznego). Mogłem zmienić nazwę modułu pobierającego w interfejsie, ale istniejąca nazwa była najbardziej logiczną nazwą obiektu. Zamiast więc zrobiłem wyraźna realizacja powołując się na polu prywatnym: UISwitch IScoreRegPlayerViewCell.markerSwitch { get { return markerSwitch; } }.
ToolmakerSteve

1
Okropności programowania. Cóż, ładnie obalony!
Płynny rdzeń

1
@ToolmakerSteve inna sytuacja (raczej powszechniejsza), która wymaga jawnej implementacji co najmniej jednego elementu interfejsu, implementuje wiele interfejsów, które mają elementy o tej samej sygnaturze, ale różnych typach zwrotów. Może się to zdarzyć z powodu interfejsu dziedziczenia, jak to robi się IEnumerator<T>.Current, IEnumerable<T>.GetEnumerator()i ISet<T>.Add(T). Jest to wspomniane w innej odpowiedzi .
phoog

201

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.

  1. Pracując bezpośrednio z interfejsem, nie potwierdzasz i nie dołączasz kodu do podstawowej implementacji.
  2. W przypadku, gdy masz już, powiedzmy, publiczną właściwość Name w swoim kodzie i chcesz zaimplementować interfejs, który również ma właściwość Name, wykonanie tej czynności jawnie rozdzieli te dwa elementy. Nawet gdyby robili to samo, nadal delegowałbym jawne wywołanie do właściwości Name. Nigdy nie wiadomo, możesz zmienić sposób działania Name dla normalnej klasy i później Name.
  3. Jeśli zaimplementujesz interfejs niejawnie, twoja klasa ujawnia teraz nowe zachowania, które mogą być istotne tylko dla klienta interfejsu i oznacza, że ​​nie trzymasz swoich klas wystarczająco zwięźle (moim zdaniem).

2
Robisz tutaj kilka dobrych punktów. szczególnie A. i tak zwykle przekazuję swoje klasy jako interfejs, ale tak naprawdę nigdy nie myślałem o tym z tej perspektywy.
mattlant

5
Nie jestem pewien, czy zgadzam się z punktem C. Obiekt Cat może implementować IEatable, ale Eat () jest podstawową częścią rzeczy. Zdarzałyby się przypadki, w których chciałbyś po prostu wywołać Eat () na Cat, gdy używasz obiektu „raw” zamiast przez interfejs IEatable, nie?
LegendLength

59
Znam niektóre miejsca, w których Catrzeczywiście IEatablenie ma zastrzeżeń.
Humberto

26
Całkowicie nie zgadzam się z powyższymi stwierdzeniami i powiedziałbym, że używanie jawnych interfejsów jest receptą katastrofy, a nie OOP ani OOD z ich definicji (zobacz moją odpowiedź na temat polimorfizmu)
Valentin Kuzub


68

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:

public abstract class StringList : IEnumerable<string>
{
    private string[] _list = new string[] {"foo", "bar", "baz"};

    // ...

    #region IEnumerable<string> Members
    public IEnumerator<string> GetEnumerator()
    {
        foreach (string s in _list)
        { yield return s; }
    }
    #endregion

    #region IEnumerable Members
    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
    #endregion
}

Tutaj IEnumerable<string>implementuje IEnumerable, 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.

StringList sl = new StringList();

// uses the implicit definition.
IEnumerator<string> enumerableString = sl.GetEnumerator();
// same as above, only a little more explicit.
IEnumerator<string> enumerableString2 = ((IEnumerable<string>)sl).GetEnumerator();
// returns the same as above, but via the explicit definition
IEnumerator enumerableStuff = ((IEnumerable)sl).GetEnumerator();

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.


5
@Tassadaque: Po 2 latach wszystko, co mogę powiedzieć, to „dobre pytanie”. Nie mam pojęcia, może poza tym, że skopiowałem ten kod z czegoś, nad czym pracowałem abstract.
Matthew Scharley,

4
@Tassadaque, masz rację, ale myślę, że punkt powyższego postu Matthew Scharleya nie został stracony.
funkymushroom

35

Przytoczyć Jeffrey Richter z CLR pośrednictwem C #
( EIMI oznacza E Xplicit I nterface M ethod I DROŻENIE)

Bardzo ważne jest, aby zrozumieć niektóre konsekwencje, które istnieją podczas korzystania z EIMI. Z powodu tych konsekwencji powinieneś starać się unikać EIMI w jak największym stopniu. Na szczęście ogólne interfejsy pomagają całkiem uniknąć EIMI. Ale wciąż mogą być chwile, kiedy będziesz musiał z nich skorzystać (na przykład zaimplementować dwie metody interfejsu o tej samej nazwie i podpisie). Oto duże problemy z EIMI:

  • Nie ma dokumentacji wyjaśniającej, w jaki sposób typ konkretnie implementuje metodę EIMI, i nie ma obsługi Microsoft Visual Studio IntelliSense .
  • Instancje typów wartości są umieszczane w ramkach, gdy są rzutowane na interfejs.
  • EIMI nie może być wywołany przez typ pochodny.

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.


2
Ponowne wdrożenie interfejsów, choć legalne, jest co najmniej co najmniej wątpliwe. Jawne implementacje powinny generalnie wiązać się z metodą wirtualną albo bezpośrednio, albo poprzez logikę zawijania, która powinna być wiążąca dla klas pochodnych. Chociaż z pewnością możliwe jest używanie interfejsów w sposób wrogi dla odpowiednich konwencji OOP, nie oznacza to, że nie można ich lepiej używać.
supercat

4
@Valentin Co oznaczają EIMI i IEMI?
Dzienny

Implementacja metody interfejsu jawnego
scobi

5
-1 dla „Generalnie widzę interfejsy jako częściowo (w najlepszym razie) funkcję OOP, zapewnia dziedziczenie, ale nie zapewnia prawdziwego polimorfizmu”. Definitywnie się z tym nie zgadzam. Wręcz przeciwnie, interfejsy dotyczą polimorfizmu i nie dotyczą przede wszystkim dziedziczenia. Przypisują wiele klasyfikacji do typu. Unikaj IEMI, kiedy możesz, i deleguj, jak sugerował @supercat, kiedy nie możesz. Nie unikaj interfejsów.
Aluan Haddad

1
„EIMI nie może zostać wywołany przez typ pochodny”. << CO? To nieprawda. Jeśli jawnie zaimplementuję interfejs dla danego typu, to wywodzę się z tego typu, nadal mogę rzutować go na interfejs, aby wywołać metodę, dokładnie tak, jak musiałbym to zrobić dla typu, w którym jest on implementowany. Więc nie jestem pewien, o czym mówisz. Nawet w typie pochodnym mogę po prostu rzucić „to” na dany interfejs, aby uzyskać jawnie zaimplementowaną metodę.
Triynko

34

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 :

public interface INavigator {
    void Redirect(string url);
}

public sealed class StandardNavigator : INavigator {
    void INavigator.Redirect(string url) {
        Response.Redirect(url);
    }
}

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:

  1. Podstawowy interfejs klasy, z którego korzystają twórcy stron internetowych; i
  2. „Ukryty” interfejs używany przez prezentera, który rozwijam do obsługi logiki sterowania

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ę.

public sealed class CustomerComboBox : ComboBox, ICustomerComboBox {
    private readonly CustomerComboBoxPresenter presenter;

    public CustomerComboBox() {
        presenter = new CustomerComboBoxPresenter(this);
    }

    protected override void OnLoad() {
        if (!Page.IsPostBack) presenter.HandleFirstLoad();
    }

    // Primary interface used by web page developers
    public Guid ClientId {
        get { return new Guid(SelectedItem.Value); }
        set { SelectedItem.Value = value.ToString(); }
    }

    // "Hidden" interface used by presenter
    IEnumerable<CustomerDto> ICustomerComboBox.DataSource { set; }
}

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.


19

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.

/// <summary>
/// This is a Book
/// </summary>
interface IBook
{
    string Title { get; }
    string ISBN { get; }
}

/// <summary>
/// This is a Person
/// </summary>
interface IPerson
{
    string Title { get; }
    string Forename { get; }
    string Surname { get; }
}

/// <summary>
/// This is some freaky book-person.
/// </summary>
class Class1 : IBook, IPerson
{
    /// <summary>
    /// This method is shared by both Book and Person
    /// </summary>
    public string Title
    {
        get
        {
            string personTitle = "Mr";
            string bookTitle = "The Hitchhikers Guide to the Galaxy";

            // What do we do here?
            return null;
        }
    }

    #region IPerson Members

    public string Forename
    {
        get { return "Lee"; }
    }

    public string Surname
    {
        get { return "Oades"; }
    }

    #endregion

    #region IBook Members

    public string ISBN
    {
        get { return "1-904048-46-3"; }
    }

    #endregion
}

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.

string IBook.Title
{
    get
    {
        return "The Hitchhikers Guide to the Galaxy";
    }
}

string IPerson.Title
{
    get
    {
        return "Mr";
    }
}

public string Title
{
    get { return "Still shared"; }
}

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.


16

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:

  • Typy wartości, ponieważ będzie to wymagało boksu i niższej perf. To nie jest ścisła zasada i zależy od interfejsu i sposobu, w jaki ma być używany. IComparable? Domniemany. IFormattable? Prawdopodobnie wyraźne.
  • Trywialne interfejsy systemowe, które mają metody często wywoływane bezpośrednio (np. IDisposable.Dispose).

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:

  1. Dodaj publikacje i przekaż im metody interfejsu do wdrożenia. Zwykle dzieje się to z prostszymi interfejsami podczas pracy wewnętrznej.
  2. (Moja preferowana metoda) Dodaj public IMyInterface I { get { return this; } }(należy wstawić) i wywołaj foo.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ę).

8

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.


1
Niezła odpowiedź, Bill. Inne odpowiedzi były świetne, ale podałeś kilka dodatkowych obiektywnych punktów widzenia (oprócz twoich opinii), które ułatwiły mi zrozumienie. Jak większość rzeczy, istnieją plusy i minusy z implementacjami niejawnymi lub jawnymi, więc musisz po prostu użyć najlepszego z nich dla konkretnego scenariusza lub przypadku użycia. Powiedziałbym, że ci, którzy próbują lepiej to zrozumieć, skorzystają na przeczytaniu twojej odpowiedzi.
Daniel Eagle,

6

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.

interface I1
{
    void implicitExample();
}

interface I2
{
    void explicitExample();
}


class C : I1, I2
{
    void implicitExample()
    {
        Console.WriteLine("I1.implicitExample()");
    }


    void I2.explicitExample()
    {
        Console.WriteLine("I2.explicitExample()");
    }
}

MSDN: niejawne i jawne implementacje interfejsu


6

Każdy członek klasy, który implementuje interfejs, eksportuje deklarację semantycznie podobną do sposobu, w jaki zapisywane są deklaracje interfejsu VB.NET, np.

Public Overridable Function Foo() As Integer Implements IFoo.Foo

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ć:

Protected Overridable Function IFoo_Foo() As Integer Implements IFoo.Foo

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 do IFoo. 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 dla IList<T>.Addmetody kolekcji tylko do odczytu to rzutowanie NotSupportedException]. Niestety jedynym prawidłowym sposobem implementacji interfejsu w języku C # jest:

int IFoo.Foo() { return IFoo_Foo(); }
protected virtual int IFoo_Foo() { ... real code goes here ... }

Nie tak miło.


2

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.


2

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 jest public.

// Given:
internal interface I { void M(); }

// Then explicit implementation correctly observes encapsulation of I:
// Both ((I)CExplicit).M and CExplicit.M are accessible only internally.
public class CExplicit: I { void I.M() { } }

// However, implicit implementation breaks encapsulation of I, because
// ((I)CImplicit).M is only accessible internally, while CImplicit.M is accessible publicly.
public class CImplicit: I { public void M() { } }

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ć publicdostęp, nawet jeśli sam interfejs to np internal.

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ą .

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.