programing

C# 인터페이스.암묵적 구현 대 명시적 구현

javamemo 2023. 5. 15. 20:58
반응형

C# 인터페이스.암묵적 구현 대 명시적 구현

C#에서 암묵적으로 그리고 명시적으로 인터페이스를 구현하는 것의 차이점은 무엇입니까?

암묵적인 사용 시기와 명시적인 사용 시기는 무엇입니까?

둘 중 하나에 장단점이 있습니까?


Microsoft의 공식 지침(Framework Design Guidelines)은 명시적 구현을 사용하는 것이 코드에 예기치 않은 동작을 제공하므로 권장하지 않는다고 명시하고 있습니다.

저는 이 지침이 사물을 인터페이스로 전달하지 않는 IoT 이전 시대에 매우 유효하다고 생각합니다.

그 측면에 대해서도 언급할 수 있는 사람이 있습니까?

Implicit은 클래스의 구성원을 통해 인터페이스를 정의하는 경우입니다.명시적이란 인터페이스의 클래스 내에서 메서드를 정의하는 경우입니다.혼란스럽게 들리겠지만 제 말은 이렇습니다.IList.CopyTo 다음과같이 됩니다.

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

다음과 같이 명시적으로:

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

차이점은 암시적 구현을 통해 인터페이스를 해당 클래스로 캐스팅하고 인터페이스 자체로 캐스팅하여 만든 클래스를 통해 인터페이스에 액세스할 수 있다는 것입니다.명시적 구현을 사용하면 인터페이스 자체로 캐스팅하여 인터페이스에 액세스할 수 있습니다.

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

기본적으로 구현을 깨끗하게 유지하거나 두 개의 구현이 필요할 때 명시적으로 사용합니다.그럼에도 불구하고, 저는 그것을 거의 사용하지 않습니다.

다른 사람들이 게시할 명시적인 사용/사용하지 않는 이유가 더 있을 것이라고 확신합니다.

각각의 배후에 있는 훌륭한 추론은 이 스레드의 다음 게시물을 참조하십시오.

암묵적인 정의는 인터페이스에서 요구하는 메소드/속성 등을 공개 메소드로 클래스에 직접 추가하는 것입니다.

명시적 정의는 기본 구현이 아닌 인터페이스로 직접 작업하는 경우에만 구성원이 노출되도록 합니다.대부분의 경우 이 방법을 사용하는 것이 방법은 다음과 같습니다.

  1. 인터페이스를 직접 사용하면 코드를 인식하지 못하고 기본 구현에 코드를 연결할 수 있습니다.
  2. 코드에 이미 공용 속성 Name이 있고 Name 속성도 있는 인터페이스를 구현하려는 경우 명시적으로 수행하면 둘이 분리됩니다.그들이 같은 일을 하고 있더라도 저는 여전히 명시적인 호출을 Name 속성에 위임했습니다.나중에 Name(이름)이 일반 클래스에서 작동하는 방식과 Name(인터페이스 속성)이 작동하는 방식을 변경할 수도 있습니다.
  3. 만약 당신이 인터페이스를 암묵적으로 구현한다면, 당신의 클래스는 이제 인터페이스의 고객에게만 관련될 수 있는 새로운 행동을 노출하게 되고, 그것은 당신이 당신의 수업을 충분히 간결하게 유지하지 못하고 있다는 것을 의미합니다(제 의견).

이미 제공된 우수한 답변 외에도 컴파일러가 필요한 내용을 파악할 수 있도록 명시적인 구현이 필요한 경우가 있습니다..IEnumerable<T>아주 자주 등장할 수 있는 대표적인 예로

다음은 예입니다.

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
}

여기서,IEnumerable<string>IEnumerable그러므로 우리도 그래야 합니다.그러나 일반 버전과 일반 버전 모두 동일한 메서드 시그니처를 사용하여 함수를 구현합니다(C#은 이에 대한 반환 유형을 무시합니다).이것은 완전히 합법적이고 벌금입니다.컴파일러는 어떤 것을 사용할지 어떻게 결정합니까?그것은 당신이 기껏해야 하나의 암묵적인 정의만 갖도록 강요합니다. 그러면 그것은 그것이 필요로 하는 모든 것을 해결할 수 있습니다.

예를

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: IEnumberable에 대한 명시적 정의의 작은 간접적인 부분은 함수 내부에서 컴파일러가 변수의 실제 유형이 StringList라는 것을 알고 있기 때문에 작동하며, 이것이 함수 호출을 해결하는 방법입니다.추상화 계층 중 일부를 구현하기 위한 매우 작은 사실.NET 코어 인터페이스가 누적된 것 같습니다.

이유 #1

저는 "구현에 대한 프로그래밍"(디자인 패턴에서 디자인 원칙)을 자제하고 싶을 때 명시적인 인터페이스 구현을 사용하는 경향이 있습니다.

예를 들어, MVP 기반 웹 애플리케이션의 경우:

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

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

이제 다른 클래스(예: 발표자)는 StandardNavigator 구현에 의존하지 않고 INavigator 인터페이스에 의존할 가능성이 높습니다(Redirect 방법을 사용하려면 구현을 인터페이스에 캐스트해야 하기 때문입니다).

이유 #2

명시적 인터페이스 구현을 사용하는 또 다른 이유는 클래스의 "기본" 인터페이스를 더 깨끗하게 유지하기 위해서입니다.예를 들어, 제가 ASP를 개발하고 있다면요.NET 서버 제어, 두 개의 인터페이스가 필요할 수 있습니다.

  1. 웹 페이지 개발자가 사용하는 클래스의 기본 인터페이스.
  2. 발표자가 컨트롤의 논리를 처리하기 위해 개발한 "숨겨진" 인터페이스

간단한 예는 다음과 같습니다.고객을 나열하는 콤보 박스 컨트롤입니다.이 예에서 웹 페이지 개발자는 목록을 작성하는 데 관심이 없습니다. 대신 GUID로 고객을 선택하거나 선택한 고객의 GUID를 얻을 수 있기를 원합니다.발표자는 첫 페이지 로드에서 상자를 채우고, 이 발표자는 컨트롤에 의해 캡슐화됩니다.

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; }
}

발표자는 데이터 소스를 채우고 웹 페이지 개발자는 데이터 소스의 존재를 알 필요가 없습니다.

하지만 은빛 대포알은 아닙니다.

항상 명시적인 인터페이스 구현을 사용하는 것은 권장하지 않습니다.그것들은 도움이 될 수 있는 두 가지 예에 불과합니다.

CLR의 의 말을 인용하자면,C# 의 C#입니다.
(EMI는 명시적 인터페이스 방법 구현을 의미함)

EIMI를 사용할 때 발생하는 몇 가지 영향을 이해하는 것이 매우 중요합니다.그리고 이러한 영향 때문에, 당신은 가능한 한 EMI를 피하도록 노력해야 합니다.다행히 일반 인터페이스는 EIMI를 상당히 방지하는 데 도움이 됩니다.그러나 동일한 이름과 서명을 사용하는 두 가지 인터페이스 방법을 구현하는 것과 같이 이러한 방법을 사용해야 하는 경우가 여전히 있을 수 있습니다.EIMI의 큰 문제는 다음과 같습니다.

  • 유형이 EIMI 메서드를 구체적으로 구현하는 방법을 설명하는 문서는 없으며 Microsoft Visual Studio IntelliSense 지원도 없습니다.
  • 값 유형 인스턴스는 인터페이스에 캐스트할 때 상자로 표시됩니다.
  • EIMI는 파생 형식으로 호출할 수 없습니다.

인터페이스 참조를 사용하는 경우 파생 클래스에서 모든 가상 체인을 EMI로 명시적으로 대체할 수 있으며 이러한 유형의 개체가 인터페이스에 캐스팅되면 가상 체인이 무시되고 명시적 구현이 호출됩니다.그건 다형성이 아닙니다.

EMI는 또한 IEnumberable과 같은 기본 프레임워크 인터페이스의 구현으로부터 강하게 유형화되지 않은 인터페이스 멤버를 숨기는 데 사용될 수 있습니다.그래서 당신의 수업은 강하게 입력되지 않은 방법을 직접적으로 노출하지 않고 구문적으로 정확합니다.

저는 대부분의 시간에 명시적인 인터페이스 구현을 사용합니다.주된 이유는 다음과 같습니다.

리팩토링이 더 안전합니다.

인터페이스를 변경할 때 컴파일러가 확인할 수 있으면 더 좋습니다.이것은 암묵적인 구현이 더 어렵습니다.

두 가지 일반적인 경우가 있습니다.

  • 인터페이스에 함수를 추가합니다. 여기서 이 인터페이스를 구현하는 기존 클래스에 서명과 동일한 서명을 가진 메서드가 이미 있습니다.이것은 예상치 못한 행동으로 이어질 수 있고, 저를 여러 번 심하게 물렸습니다.디버깅할 때 해당 함수가 파일의 다른 인터페이스 메서드와 함께 있지 않을 가능성이 높기 때문에 "확인"하기 어렵습니다(아래에 언급된 자체 문서화 문제).

  • 인터페이스에서 함수를 제거합니다.암시적으로 구현된 메서드는 갑자기 데드 코드가 되지만 명시적으로 구현된 메서드는 컴파일 오류로 인해 탐지됩니다.데드코드가 곁에 두어도 어쩔 수 없이 검토해서 홍보하고 싶습니다.

안타깝게도 C#에는 메서드를 암시적 구현으로 표시하도록 강제하는 키워드가 없으므로 컴파일러가 추가 검사를 수행할 수 있습니다.가상 메소드는 'override'와 'new'를 필수적으로 사용하기 때문에 위의 문제가 없습니다.

참고: 고정되거나 거의 변경되지 않는 인터페이스(일반적으로 공급업체 API)의 경우에는 문제가 없습니다.하지만 내 인터페이스의 경우 언제/어떻게 바뀔지 예측할 수 없습니다.

자체 문서화입니다.

클래스에서 'public bool Execute()'가 표시되면 인터페이스의 일부인지 확인하는 데 추가 작업이 필요합니다.누군가가 이를 언급하거나 다른 인터페이스 구현 그룹에 포함시켜 "IT 작업 구현"이라는 지역 또는 그룹 의견을 제시해야 할 것입니다.물론 그룹 헤더가 화면 밖에 없을 때만 작동합니다.

반면: 'bool IT 태스크'.Execute()'는 명확하고 모호하지 않습니다.

인터페이스 구현의 명확한 분리

저는 인터페이스가 콘크리트 유형의 표면적의 일부만 노출되도록 만들어졌기 때문에 공개적인 방법보다 더 '공개적인' 것이라고 생각합니다.그들은 유형을 능력, 행동, 특성 등으로 줄입니다.그리고 구현에서는 이러한 분리를 유지하는 것이 유용하다고 생각합니다.

클래스의 코드를 살펴보고 있을 때, 명시적인 인터페이스 구현을 접하면 뇌가 "코드 계약" 모드로 전환됩니다.종종 이러한 구현은 단순히 다른 방법으로 전달되지만, 때때로 추가적인 상태/매개 변수 확인, 내부 요구 사항에 더 부합하도록 들어오는 매개 변수의 변환 또는 버전 관리 목적(즉, 여러 세대의 인터페이스가 모두 공통 구현으로 연결됨)을 위한 변환을 수행합니다.

(퍼블릭도 코드 계약이지만, 특히 콘크리트 유형을 직접 사용하는 것이 일반적으로 내부 전용 코드의 신호인 인터페이스 중심 코드베이스에서 인터페이스가 훨씬 더 강하다는 것을 알고 있습니다.)

관련:의 이유 2는 존에 의한 것입니다.

등등

여기에 있는 다른 답변에서 이미 언급한 이점도 있습니다.

  • 필요한 경우, 모호성이 없거나 내부 인터페이스가 필요한 경우
  • "구현을 위한 프로그래밍"을 금지합니다(Jon의 이유 1).

문제

재미와 행복이 전부는 아닙니다.제가 암시를 고수하는 경우가 있습니다.

  • 가치 유형. 복싱과 낮은 성능이 필요하기 때문입니다.이것은 엄격한 규칙이 아니며 인터페이스와 사용 방법에 따라 다릅니다.비교할 수 있을까요?암묵적인.제가 테이블을 만들까요분명할 겁니다.
  • ID가 일회용인 것처럼 자주 직접 호출되는 메서드가 있는 사소한 시스템 인터페이스입니다.폐기).

또한, 실제로 구체적인 유형을 가지고 있고 명시적인 인터페이스 방법을 호출하고자 할 때 주조 작업을 수행하는 것이 번거로울 수 있습니다.저는 이 문제를 두 가지 방법 중 하나로 처리합니다.

  1. 공개를 추가하고 구현을 위해 인터페이스 메서드를 전달하도록 합니다.일반적으로 내부적으로 작업할 때 단순한 인터페이스에서 발생합니다.
  2. 가 선호하는 방법) (추가선하는방법) 가내public IMyInterface I { get { return this; } }이 서야 함) (줄이 서야 함)을 호출합니다foo.I.InterfaceMethod()이 기능이 필요한 인터페이스가 여러 개인 경우 I 이상으로 이름을 확장합니다(내 경험으로는 이러한 기능이 필요한 경우는 거의 없습니다).

이미 언급한 다른 이유 외에도, 이는 클래스가 동일한 이름과 서명을 가진 속성/메소드를 가진 두 개의 서로 다른 인터페이스를 구현하는 상황입니다.

/// <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
}

이 코드는 컴파일되어 정상적으로 실행되지만 제목 속성은 공유됩니다.

분명히, 우리는 우리가 클래스 1을 책으로 취급하는지 사람으로 취급하는지에 따라 타이틀의 가치가 반환되기를 원합니다.이것은 우리가 명시적인 인터페이스를 사용할 수 있는 때입니다.

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

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

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

명시적 인터페이스 정의는 공개로 유추되므로 공개(또는 다른 방식)로 명시적으로 선언할 수 없습니다.

또한 위와 같이 "공유" 버전을 계속 사용할 수 있지만 이러한 속성이 있는지는 의문입니다.아마도 그것은 타이틀의 기본 구현으로 사용될 수 있을 것입니다 - 그래서 클래스 1을 IBook 또는 IPerson에 캐스팅하기 위해 기존 코드를 수정할 필요가 없습니다.

"공유"를 정의하지 않는 경우(암묵적)제목, 클래스 1의 소비자는 클래스 1의 인스턴스를 IBook 또는 IP 사용자에게 먼저 명시적으로 캐스팅해야 합니다. 그렇지 않으면 코드가 컴파일되지 않습니다.

명시적으로 구현하는 경우 인터페이스 유형의 참조를 통해서만 인터페이스 멤버를 참조할 수 있습니다.구현 클래스의 유형인 참조는 이러한 인터페이스 멤버를 노출시키지 않습니다.

구현 클래스가 공개되지 않은 경우 클래스를 만드는 데 사용된 메서드(공장 또는 IoC 컨테이너일 수 있음)와 인터페이스 메서드(물론)를 제외하고는 인터페이스를 명시적으로 구현하는 데 아무런 이점이 없습니다.

그렇지 않으면 인터페이스를 명시적으로 구현하면 구체적인 구현 클래스에 대한 참조가 사용되지 않으므로 나중에 구현을 변경할 수 있습니다."확실히"가 "장점"이라고 생각합니다.요소가 잘 포함된 구현은 명시적인 구현 없이도 이를 달성할 수 있습니다.

단점은 공개되지 않은 구성원에게 액세스할 수 있는 구현 코드에서 인터페이스에 유형을 캐스팅한다는 것입니다.

다른 많은 것들과 마찬가지로, 장점은 단점입니다(그 반대도 마찬가지입니다.인터페이스를 명시적으로 구현하면 구체적인 클래스 구현 코드가 노출되지 않습니다.

암시적 인터페이스 구현은 인터페이스의 동일한 서명을 가진 메서드를 사용하는 것입니다.

명시적 인터페이스 구현은 메소드가 속한 인터페이스를 명시적으로 선언하는 것입니다.

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: 암묵적 및 명시적 인터페이스 구현

인터페이스를 구현하는 모든 클래스 멤버는 VB 방식과 의미적으로 유사한 선언을 내보냅니다.NET 인터페이스 선언은 다음과 같이 작성됩니다.

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

클래스 구성원의 이름이 인터페이스 구성원의 이름과 일치하고 클래스 구성원이 공개되는 경우가 종종 있지만, 두 가지 모두 필요하지 않습니다.다음과 같이 선언할 수도 있습니다.

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

파생물이 에게 이름 " 스와클파이이사름클용여경멤우액있수는세할스버에래"를 수 .IFoo_Foo하지만 외부 세계는 캐스팅을 통해서만 특정 멤버에게 접근할 수 있습니다.IFoo이러한 접근 방식은 인터페이스 메소드가 모든 구현에 대해 지정된 동작을 가지지만 일부 [예: 읽기 전용 컬렉션에 대한 지정된 동작]에서만 유용한 동작을 갖는 경우에 종종 좋습니다.IList<T>.Add는 방은던것입다니는지법▁throw다▁is를 던지는 것입니다.NotSupportedException를 구현할 수 은:_unfortuny]입니다

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

별로 좋지 않습니다.

이전 답변은 C#에서 인터페이스를 명시적으로 구현하는 이 선호되는 이유를 설명합니다(대부분 공식적인 이유).그러나 명시적 구현이 필수적인 한 가지 상황이 있습니다.인터페이스가 비-일 때 캡슐화가 누출되는 것을 방지하기 위해public은 지만실행수업은하▁is수입니다.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() { } }

C# 규격에 따르면 "모든 인터페이스 구성원은 암묵적으로 공용 액세스 권한을 가집니다." 때문에 상기 누설은 피할 수 없습니다.결과적으로, 암묵적인 구현은 또한 다음을 제공해야 합니다.public인터페이스 자체가 예를 들어.internal.

C#에서 암묵적인 인터페이스 구현은 매우 편리합니다.실제로 많은 프로그래머들이 더 이상 고려하지 않고 항상/어디서나 사용합니다.이로 인해 기껏해야 유형 표면이 지저분해지고 최악의 경우 캡슐화가 누출됩니다.F#와 같은 다른 언어들은 심지어 그것을 허용하지 않습니다.

명시적 인터페이스 구현의 중요한 사용 중 하나는 가시성이 혼합된 인터페이스를 구현해야 할 때입니다.

문제와 해결책은 C# Internal Interface 기사에 잘 설명되어 있습니다.

예를 들어 응용 프로그램 계층 간의 개체 누출을 보호하려면 이 기법을 사용하여 누출을 유발할 수 있는 구성원의 다른 가시성을 지정할 수 있습니다.

최근에는 다음과 같은 실질적인 이유로 명시적 구현을 더 자주 사용하고 있습니다.

  • 항상 시작부터 명시적으로 사용하면 명명 충돌을 방지할 수 있으며, 이 경우 명시적 구현이 필요합니다.

  • 소비자는 DI를 사용할 때 반드시 해야 하는 구현("구현으로 프로그래밍"하지 않음) 대신 인터페이스를 사용하도록 "강제"됩니다.

  • 구현에 "좀비" 멤버가 없음 - 인터페이스 선언에서 멤버를 제거하면 구현에서도 제거되지 않으면 컴파일러 오류가 발생합니다.

  • 선택적 매개 변수의 기본값과 일반 인수의 제약 조건이 자동으로 채택되므로 매개 변수를 두 번 작성하여 동기화 상태로 유지할 필요가 없습니다.

언급URL : https://stackoverflow.com/questions/143405/c-sharp-interfaces-implicit-implementation-versus-explicit-implementation

반응형