Interfejsy C#. Implicit implementation versus Explicit implementation

Jakie są różnice w implementacji interfejsów implicite i explicite W C#?

Kiedy należy używać implicit, a kiedy explicit?

Czy są jakieś plusy i / lub minusy dla jednego lub drugiego?


Oficjalne wytyczne Microsoftu (z pierwszej edycji Framework Design Guidelines) stwierdzają, że używanie jawnych implementacji nie jest zalecane , ponieważ daje kodowi nieoczekiwane zachowanie.

I myślę, że ta wskazówka jest bardzo ważna w czasie przed IoC, Kiedy nie przekazujesz rzeczy jako interfejsów.

Czy ktoś mógłby dotknąć również tego aspektu?

Author: Guillermo Gutiérrez, 2008-09-27

11 answers

Implicit jest wtedy, gdy definiujesz swój interfejs za pośrednictwem członka na twojej klasie. Explicit jest wtedy, gdy definiujesz metody w swojej klasie na interfejsie. Wiem, że brzmi to myląco, ale mam na myśli to, co mam na myśli: IList.CopyTo byłoby zaimplementowane jako:

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

I jawnie jako:

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

Różnica polega na tym, że domyślnie jest dostępna poprzez klasę, którą utworzyłeś, gdy jest rzucana jako ta klasa, a także gdy jest rzucana jako interfejs. Explicit implementacja pozwala na to, aby był dostępny tylko wtedy, gdy cast jako sam interfejs.

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

Używam explicit przede wszystkim, aby utrzymać implementację w czystości, lub gdy potrzebuję dwóch implementacji. Ale mimo to rzadko go używam.

Jestem pewien, że jest więcej powodów, aby go używać/nie używać, które inni opublikują.

Zobacz next post w tym wątku dla doskonałego rozumowania za każdym.

 447
Author: mattlant,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:32

Definicja Implicit będzie po prostu dodać metody / właściwości, itp. wymagane przez interfejs bezpośrednio do klasy jako metody publiczne.

Jawna definicja wymusza na członkach ujawnienie się tylko wtedy, gdy pracujesz bezpośrednio z interfejsem, a nie z podstawową implementacją. Jest to preferowane w większości przypadków.

  1. pracując bezpośrednio z interfejsem, Nie potwierdzasz, i łączenie kodu z podstawową implementacją.
  2. W zdarzenie, które ma już np. nazwę własności publicznej w Twój kod i chcesz zaimplementować interfejs, który ma również Nazwa właściwości, robi to jawnie będzie zachować dwa oddzielne. Parzyste gdyby robili to samo, to i tak delegowałbym wyraźne zadzwoń do nieruchomości. Nigdy nie wiesz, możesz chcieć zmienić jak Name działa dla normalnej klasy i jak Name, interfejs nieruchomość działa później.
  3. jeśli zaimplementujesz interfejs w sposób niejawny, wtedy twoja klasa ujawni nowe zachowania, które mogą być istotne tylko dla klienta z interfejs i oznacza to, że nie utrzymujesz zwięzłych zajęć dość (moim zdaniem).
 180
Author: Phil Bennett,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-04 18:32:55

Oprócz doskonałych odpowiedzi już udzielonych, istnieją przypadki, w których Jawna implementacja jest wymagana, aby kompilator mógł dowiedzieć się, co jest wymagane. Spójrz na IEnumerable<T> jako doskonały przykład, który prawdopodobnie pojawi 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, stąd też musimy Ale poczekaj, zarówno ogólna, jak i normalna wersja implementują funkcje z tą samą sygnaturą metody (C# ignoruje return type for this). To jest całkowicie legalne i w porządku. Jak kompilator rozwiązuje, którego użyć? Zmusza cię do posiadania tylko, co najwyżej, jednej ukrytej definicji, wtedy może rozwiązać wszystko, czego potrzebuje.

Ie.

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 indrection w jawnej definicji IEnumerable działa, ponieważ wewnątrz funkcji kompilator wie, że rzeczywisty typ zmiennej jest listą StringList i w ten sposób rozwiązuje wywołanie funkcji. Fajny fakt dla implementacja niektórych warstw abstrakcji wydaje się, że niektóre z podstawowych interfejsów.NET zgromadziły się.

 63
Author: Matthew Scharley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-07 11:09:44

Powód # 1

Zazwyczaj używam jawnej implementacji interfejsu, gdy chcę zniechęcić "Programowanie 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 (taka jak presenter) jest mniej zależna od standardowej implementacji i bardziej zależna od interfejsu Inavigatora (ponieważ implementacja musiałaby być cast to a interface to make use of the Redirect method).

Powód # 2

Innym powodem, dla którego mógłbym użyć jawnej implementacji interfejsu, byłoby zachowanie "domyślnego" czyszczenia interfejsu klasy. Na przykład, gdybym rozwijał ASP.NET sterowanie serwerem, przydałyby mi się dwa interfejsy:

    [25]}podstawowy interfejs klasy, który jest używany przez twórców stron internetowych; oraz {26]}
  1. "Ukryty" interfejs używany przez prezentera, który rozwijam do obsługi logika sterowania

Oto prosty przykład. To kontrolka combo box, która wyświetla listę klientów. W tym przykładzie programista stron internetowych nie jest zainteresowany wypełnianiem listy; zamiast tego chce po prostu wybrać klienta za pomocą identyfikatora GUID lub uzyskać identyfikator GUID wybranego klienta. Prezenter wypełniłby to pole przy pierwszym załadowaniu strony, a ten prezenter jest zamknięty 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 zapełnia źródło danych, a twórca strony www nigdy musi być świadomy jego istnienia.

Ale to nie jest Srebrna kula armatnia.]}

Nie zalecałbym stosowania jawnych implementacji interfejsu. To tylko dwa przykłady, w których mogą być pomocne.

 29
Author: Jon Nadal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-23 17:17:00

Cytuję Jeffrey Richter z CLR via C#
(EIMI oznacza E xplicit I nterface M ethod I mplementation)

Jest niezwykle ważne, aby zrozumieć pewne konsekwencje, które istnieją podczas korzystania z EIMIs. I ze względu na te konsekwencje, powinieneś spróbować unikaj EIMIs tak bardzo, jak to możliwe. Na szczęście interfejsy generyczne pomagają dość często unikasz EIMIs. Ale tam może nadal być czas, kiedy będziesz potrzebować do użycia nich (np. realizacja dwóch metody interfejsu o tej samej nazwie i podpis). Oto wielkie problemy z EIMIs:

  • nie ma dokumentacji wyjaśniającej, w jaki sposób Typ wdraża metodę EIMI, a tam nie jest Microsoft Visual Studio IntelliSense Wsparcie.
  • instancje typu wartości są boxowane podczas przesyłania do interfejsu.
  • EIMI nie może być wywołane przez typ Pochodny.

Jeśli używasz odniesienie do interfejsu każdy wirtualny łańcuch może być jawnie zastąpiony przez EIMI na dowolnej klasie pochodnej, a gdy obiekt tego typu jest rzucany do interfejsu, Twój wirtualny łańcuch jest ignorowany i wywoływana jest jawna implementacja. To nic innego jak polimorfizm.

EIMIs może być również używany do ukrywania nie - silnie wpisanych elementów interfejsu z podstawowych implementacji interfejsów Frameworkowych, takich jak IEnumerable, więc twoja klasa nie ujawnia bezpośrednio nie-mocno wpisanych metod, ale jest składniowa zgadza się.

 29
Author: Valentin Kuzub,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-23 17:22:41

Oprócz innych powodów już podanych, jest to sytuacja, w której klasa implementuje dwa różne interfejsy, które mają właściwość / metodę o tej samej nazwie i podpisie.

/// <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 i działa poprawnie, ale właściwość Title jest współdzielona.

Oczywiście chcielibyśmy, aby wartość tytułu zależała od tego, czy traktujemy Class1 jako książkę, czy osobę. 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 wyraźne definicje interfejsu są wnioskowane jako publiczne - a zatem nie można jawnie zadeklarować ich jako publiczne (lub w inny sposób).

Zauważ również, ż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 mógłby być użyty jako domyślna implementacja Title - tak aby istniejący kod nie musiał być modyfikowany tak, aby cast Class1 do IBook lub IPerson.

Jeśli nie zdefiniujesz tytułu "shared" (implicit) , użytkownicy Class1 muszą jawnie oddać instancje Class1 do IBook lub IPerson jako pierwsi - w przeciwnym razie kod nie zostanie skompilowany.

 17
Author: Lee Oades,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-23 17:12:57

Przez większość czasu używam jawnej implementacji interfejsu. Oto główne powody.

Refaktoryzacja jest bezpieczniejsza

Podczas zmiany interfejsu, lepiej, jeśli kompilator może to sprawdzić. Jest to trudniejsze w przypadku implementacji ukrytych.

Na myśl przychodzą dwa typowe przypadki:

  • Dodawanie funkcji do interfejsu, gdzie istniejąca klasa, która implementuje ten interfejs, ma już metodę o tym samym podpisie, co Nowa jeden . Może to prowadzić do nieoczekiwanego zachowania i ugryzł mnie mocno kilka razy. Jest to trudne do "zobaczenia" podczas debugowania, ponieważ funkcja ta prawdopodobnie nie jest zlokalizowana z innymi metodami interfejsu w pliku (problem samodokumentowania wymieniony poniżej).

  • usuwanie funkcji z interfejsu . Domyślnie zaimplementowane metody będą nagle martwym kodem, ale jawnie zaimplementowane metody zostaną przechwycone przez błąd kompilacji. Nawet jeśli martwy kod jest dobry do trzymaj się, chcę być zmuszony do przeglądu i promowania go.

To niefortunne, że C# nie ma słowa kluczowego, które zmusza nas do oznaczania metody jako domyślnej implementacji, więc kompilator może wykonać dodatkowe kontrole. Metody wirtualne nie mają żadnego z powyższych problemów ze względu na wymagane użycie 'override' i 'new'.

Uwaga: w przypadku stałych lub rzadko zmieniających się interfejsów (zazwyczaj z API dostawcy) nie stanowi to problemu. Dla własnych interfejsów, choć nie mogę przewidzieć kiedy / jak się zmienią.

To samo dokumentowanie

Jeśli zobaczę 'public bool Execute ()' w klasie, będzie to wymagało dodatkowej pracy, aby dowiedzieć się, że jest to część interfejsu. Ktoś prawdopodobnie będzie musiał to skomentować, mówiąc Tak, lub umieścić go w grupie innych implementacji interfejsu, wszystkie pod regionem lub komentarzem grupującym mówiącym "implementacja ITask". Oczywiście działa to tylko wtedy, gdy nagłówek grupy nie jest poza ekranem..

"Bool ITask.Execute () " jest jasne i jednoznaczne.

Wyraźne oddzielenie implementacji interfejsu

Myślę, że interfejsy są bardziej "publiczne" niż metody publiczne, ponieważ są tworzone tak, aby odsłonić tylko część powierzchni danego typu betonu. Redukują typ do zdolności, zachowania, zestawu cech itp. I w realizacji, myślę, że warto zachować tę separację.

Kiedy przeglądam Kod klasy, kiedy natkam się na jawny interfejs implementacje, mój mózg przechodzi w tryb "code contract". Często implementacje te po prostu przechodzą do innych metod, ale czasami wykonują dodatkowe sprawdzanie stanu/param, konwersję przychodzących parametrów w celu lepszego dopasowania do wymagań wewnętrznych, a nawet tłumaczenie do celów wersjonowania (tj. wiele generacji interfejsów, wszystkie obciążające do wspólnych implementacji).

(zdaję sobie sprawę, że to także umowy kodowe, ale interfejsy są znacznie silniejsze, zwłaszcza w kod oparty na interfejsie, w którym bezpośrednie użycie konkretnych typów jest zwykle oznaką kodu wewnętrznego.)

Powiązane: powód 2 powyżej przez Jon .

I tak dalej

Plus zalety już wymienione w innych odpowiedziach tutaj:

    W zależności od tego, czy jest to wymagane, czy też wymaga wewnętrznego interfejsu (np.]}
  • zniechęca do "programowania do implementacji" ( powód 1 przez Jon )

Problemy

To nie tylko zabawa i szczęście. Są przypadki, w których trzymam się niejawnych wartości:
  • typy wartości, ponieważ będzie to wymagało boksu i niższego perf. Nie jest to ścisła reguła i zależy od interfejsu i sposobu, w jaki ma być używany. Porównywalny? Ukryte. Iformatable? Pewnie wyraźnie.
  • trywialne interfejsy systemowe, które mają metody, które są często wywoływane bezpośrednio (jak To możliwe.Usunąć).

Również, to może być ból zrobić odlewania, gdy w rzeczywistości masz konkretny typ i chcesz wywołać jawną metodę interfejsu. Mam do czynienia z tym na dwa sposoby:

  1. Dodaj publics i przekaż metody interfejsu do ich implementacji. Zwykle dzieje się to z prostszymi interfejsami podczas pracy wewnętrznej.
  2. (moja preferowana metoda) Dodaj public IMyInterface I { get { return this; } } (która powinna zostać zainlinowana) i wywołaj foo.I.InterfaceMethod(). Jeśli wiele interfejsów, które wymagają ta umiejętność, rozszerzyć nazwę poza mnie (z mojego doświadczenia to rzadkość, że mam tę potrzebę).
 11
Author: scobi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:41

Jeśli zaimplementujesz jawnie, będziesz mógł odwoływać się do członków interfejsu tylko poprzez odniesienie, które jest typu interfejsu. Odniesienie, które jest typem klasy implementującej, nie ujawni tych członkó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ć fabrycznym lub IOC kontenerem), i z wyjątkiem metod interfejsu (oczywiście) , to nie widzę żadnej korzyści, aby jawnie implementowanie interfejsów.

W Przeciwnym Razie jawnie implementujące interfejsy upewniają się, że odniesienia do konkretnej klasy implementującej nie są używane, co pozwala na zmianę tej implementacji w późniejszym czasie. "Upewnia", jak sądzę, jest "zaletą". Dobrze przemyślane wdrożenie może tego dokonać bez wyraźnego wdrożenia.

Wadą moim zdaniem jest to, że w kodzie implementacyjnym znajdziesz typy do/z interfejsu, które mają dostęp do członków niepublicznych.

Jak wiele rzeczy, zaletą jest wada (i vice-versa). Jawne implementowanie interfejsów zapewni, że twój konkretny kod implementacji klas nie zostanie ujawniony.

 8
Author: Bill,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-17 18:28:25

Implicit interface implementation jest tam, gdzie masz metodę o tej samej sygnaturze interfejsu.

Jawna implementacja interfejsu to miejsce, w którym jawnie deklarujesz, do którego interfejsu 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: implementacje interfejsów niejawnych i jawnych

 6
Author: Yochai Timmer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-26 14:12:52

Każdy członek klasy implementujący interfejs eksportuje deklarację, która jest semantycznie podobna do VB.NET deklaracje interfejsu są zapisywane, np.

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

Chociaż nazwa członka klasy często będzie pasować do nazwy członka interfejsu, a członek klasy często będzie 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 będą mogły uzyskać dostęp do członka klasy używając nazwy IFoo_Foo, ale świat zewnętrzny byłby w stanie uzyskać dostęp do tego konkretnego członka tylko poprzez casting do IFoo. Takie podejście jest często dobre w przypadkach, gdy metoda interfejsu będzie miała określone zachowanie we wszystkich implementacjach, ale użyteczne zachowanie tylko na niektórych[np. określone zachowanie dla metody IList<T>.Add kolekcji tylko do odczytu polega na rzuceniu NotSupportedException]. Niestety, jedynym właściwym sposobem implementacji interfejsu w C# jest:

int IFoo.Foo() { return IFoo_Foo(); }
protected virtual int IFoo_Foo() { ... real code goes here ... }
Nie tak miło.
 5
Author: supercat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-23 17:29:12

Jednym z ważnych zastosowań jawnej implementacji interfejsu jest implementacja interfejsów z Mixed visibility .

Problem i rozwiązanie są dobrze wyjaśnione w artykule Interfejs wewnętrzny C# .

Na przykład, jeśli chcesz chronić wyciek obiektów między warstwami aplikacji, ta technika pozwala określić różną widoczność elementów, które mogą spowodować wyciek.

 1
Author: nrodic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-23 17:27:47