C # 인터페이스. 암시 적 구현과 명시 적 구현
C #에서 암시 적으로 그리고 명시 적으로 인터페이스를 구현할 때의 차이점은 무엇입니까 ?
언제 암시 적을 사용해야하고 언제 명시 적을 사용해야합니까?
둘 중 하나에 장단점이 있습니까?
Microsoft의 공식 지침 (초판 Framework Design Guidelines에서 발췌 )에 따르면 명시 적 구현 을 사용 하는 것은 코드에 예기치 않은 동작을 제공하므로 권장되지 않습니다 .
이 가이드 라인은 인터페이스로 전달하지 않는 IoC 이전 시간에 매우 유효 하다고 생각합니다 .
누구든지 그 측면을 만질 수 있습니까?
암시 적이 란 클래스의 멤버를 통해 인터페이스를 정의하는 것입니다. 명시 적은 인터페이스의 클래스 내에서 메서드를 정의하는 경우입니다. 혼란스럽게 들리지만 여기에 의미 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.
구현을 깨끗하게 유지하거나 두 가지 구현이 필요할 때 주로 명시 적을 사용합니다. 그러나 나는 그것을 거의 사용하지 않는다.
다른 사람들이 게시하게 될 더 많은 이유가 있다고 확신합니다.
각각에 대한 훌륭한 추론은이 스레드 의 다음 게시물 을 참조하십시오 .
암시 적 정의는 인터페이스에서 요구하는 메서드 / 속성 등을 공용 메서드로 클래스에 직접 추가하는 것입니다.
명시 적 정의는 기본 구현이 아닌 인터페이스로 직접 작업 할 때만 멤버가 노출되도록합니다. 이것은 대부분의 경우 선호됩니다.
- 인터페이스로 직접 작업하면 코드를 인식하지 않고 기본 구현에 연결하는 것이 아닙니다.
- 코드에 이미 공용 속성 Name이 있고 Name 속성도있는 인터페이스를 구현하려는 경우 명시 적으로 수행하면 두 가지가 별도로 유지됩니다. 그들이 똑같은 일을하고 있더라도 Name 속성에 대한 명시 적 호출을 위임합니다. 이름이 일반 클래스에 대해 작동하는 방식과 Name, 인터페이스 속성이 나중에 작동하는 방식을 변경하고 싶을 수도 있습니다.
- 암시 적으로 인터페이스를 구현하면 이제 클래스가 인터페이스의 클라이언트에만 관련 될 수있는 새로운 동작을 노출하며 이는 클래스를 충분히 간결하게 유지하지 않는다는 것을 의미합니다 (내 의견).
이미 제공된 훌륭한 답변 외에도 컴파일러가 필요한 것을 파악할 수 있도록 명시 적 구현이 필요한 경우가 있습니다. 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();
추신 : IEnumerable에 대한 명시 적 정의의 작은 부분은 함수 내에서 컴파일러가 변수의 실제 유형이 StringList라는 것을 알고 있기 때문에 작동하며, 이것이 함수 호출을 해결하는 방법입니다. .NET 핵심 인터페이스 중 일부를 추상화 계층을 구현하는 데있어 아주 작은 사실이 축적 된 것 같습니다.
이유 # 1
나는 "구현에 대한 프로그래밍"( 디자인 패턴의 디자인 원칙) 을 막고 싶을 때 명시 적 인터페이스 구현을 사용하는 경향이 있습니다 .
예를 들어 MVP 기반 웹 응용 프로그램에서 :
public interface INavigator {
void Redirect(string url);
}
public sealed class StandardNavigator : INavigator {
void INavigator.Redirect(string url) {
Response.Redirect(url);
}
}
이제 다른 클래스 (예 : presenter )는 StandardNavigator 구현에 의존 할 가능성이 적고 INavigator 인터페이스에 의존 할 가능성이 더 큽니다 (구현이 Redirect 메서드를 사용하려면 인터페이스로 캐스트되어야하기 때문입니다).
이유 # 2
명시 적 인터페이스 구현을 사용하는 또 다른 이유는 클래스의 "기본"인터페이스를 깔끔하게 유지하기 위해서입니다. 예를 들어 ASP.NET 서버 컨트롤 을 개발하는 경우 두 가지 인터페이스가 필요할 수 있습니다.
- 웹 페이지 개발자가 사용하는 클래스의 기본 인터페이스입니다. 과
- 컨트롤의 논리를 처리하기 위해 개발 한 발표자가 사용하는 "숨겨진"인터페이스
다음은 간단한 예입니다. 고객을 나열하는 콤보 상자 컨트롤입니다. 이 예에서 웹 페이지 개발자는 목록을 채우는 데 관심이 없습니다. 대신 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; }
}
발표자는 데이터 소스를 채우고 웹 페이지 개발자는 데이터 소스의 존재를 알 필요가 없습니다.
하지만 실버 캐논볼이 아니야
항상 명시적인 인터페이스 구현을 사용하는 것은 권장하지 않습니다. 이는 도움이 될 수있는 두 가지 예일뿐입니다.
C #을 통해 CLR에서 제프리 리히터의 말을 인용
( 에이미는 의미 E xplicit I nterface M은 ethod I의 mplementation)
EIMI를 사용할 때 존재하는 몇 가지 결과를 이해하는 것이 매우 중요합니다. 그리고 이러한 파급 효과 때문에 가능한 한 EIMI를 피해야합니다. 다행히도 일반 인터페이스는 EIMI를 상당히 피하는 데 도움이됩니다. 그러나이를 사용해야하는 경우가있을 수 있습니다 (예 : 동일한 이름과 서명으로 두 개의 인터페이스 메서드 구현). EIMI의 큰 문제점은 다음과 같습니다.
- 형식이 EIMI 메서드를 구체적으로 구현하는 방법을 설명하는 문서가 없으며 Microsoft Visual Studio IntelliSense 지원도 없습니다.
- 값 유형 인스턴스는 인터페이스로 캐스트 될 때 박스로 표시됩니다.
- EIMI는 파생 유형에서 호출 할 수 없습니다.
인터페이스 참조를 사용하는 경우 모든 파생 클래스에서 가상 체인을 EIMI로 명시 적으로 바꿀 수 있으며 이러한 유형의 개체가 인터페이스로 캐스팅되면 가상 체인이 무시되고 명시 적 구현이 호출됩니다. 그것은 다형성이 아닙니다.
EIMI는 IEnumerable <T>와 같은 기본 프레임 워크 인터페이스의 구현에서 강력하지 않은 형식의 인터페이스 멤버를 숨기는데도 사용할 수 있으므로 클래스가 강력하지 않은 형식의 메서드를 직접 노출하지 않지만 구문은 정확합니다.
이미 언급 된 다른 이유 외에도 이것은 클래스가 동일한 이름과 서명을 가진 속성 / 메서드를 가진 두 개의 다른 인터페이스를 구현하는 상황입니다.
/// <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
}
This code compiles and runs OK, but the Title property is shared.
Clearly, we'd want the value of Title returned to depend on whether we were treating Class1 as a Book or a Person. This is when we can use the explicit interface.
string IBook.Title
{
get
{
return "The Hitchhikers Guide to the Galaxy";
}
}
string IPerson.Title
{
get
{
return "Mr";
}
}
public string Title
{
get { return "Still shared"; }
}
Notice that the explicit interface definitions are inferred to be Public - and hence you can't declare them to be public (or otherwise) explicitly.
Note also that you can still have a "shared" version (as shown above), but whilst this is possible, the existence of such a property is questionable. Perhaps it could be used as a default implementation of Title - so that existing code would not have to be modified to cast Class1 to IBook or IPerson.
If you do not define the "shared" (implicit) Title, consumers of Class1 must explicitly cast instances of Class1 to IBook or IPerson first - otherwise the code will not compile.
I use explicit interface implementation most of the time. Here are the main reasons.
Refactoring is safer
When changing an interface, it's better if the compiler can check it. This is harder with implicit implementations.
Two common cases come to mind:
Adding a function to an interface, where an existing class that implements this interface already happens to have a method with the same signature as the new one. This can lead to unexpected behavior, and has bitten me hard several times. It's difficult to "see" when debugging because that function is likely not located with the other interface methods in the file (the self-documenting issue mentioned below).
Removing a function from an interface. Implicitly implemented methods will be suddenly dead code, but explicitly implemented methods will get caught by compile error. Even if the dead code is good to keep around, I want to be forced to review it and promote it.
It's unfortunate that C# doesn't have a keyword that forces us to mark a method as an implicit implementation, so the compiler could do the extra checks. Virtual methods don't have either of the above problems due to required use of 'override' and 'new'.
Note: for fixed or rarely-changing interfaces (typically from vendor API's), this is not a problem. For my own interfaces, though, I can't predict when/how they will change.
It's self-documenting
If I see 'public bool Execute()' in a class, it's going to take extra work to figure out that it's part of an interface. Somebody will probably have to comment it saying so, or put it in a group of other interface implementations, all under a region or grouping comment saying "implementation of ITask". Of course, that only works if the group header isn't offscreen..
Whereas: 'bool ITask.Execute()' is clear and unambiguous.
Clear separation of interface implementation
I think of interfaces as being more 'public' than public methods because they are crafted to expose just a bit of the surface area of the concrete type. They reduce the type to a capability, a behavior, a set of traits, etc. And in the implementation, I think it's useful to keep this separation.
As I am looking through a class's code, when I come across explicit interface implementations, my brain shifts into "code contract" mode. Often these implementations simply forward to other methods, but sometimes they will do extra state/param checking, conversion of incoming parameters to better match internal requirements, or even translation for versioning purposes (i.e. multiple generations of interfaces all punting down to common implementations).
(I realize that publics are also code contracts, but interfaces are much stronger, especially in an interface-driven codebase where direct use of concrete types is usually a sign of internal-only code.)
Related: Reason 2 above by Jon.
And so on
Plus the advantages already mentioned in other answers here:
- When required, as per disambiguation or needing an internal interface
- Discourages "programming to an implementation" (Reason 1 by Jon)
Problems
It's not all fun and happiness. There are some cases where I stick with implicits:
- Value types, because that will require boxing and lower perf. This isn't a strict rule, and depends on the interface and how it's intended to be used. IComparable? Implicit. IFormattable? Probably explicit.
- Trivial system interfaces that have methods that are frequently called directly (like IDisposable.Dispose).
Also, it can be a pain to do the casting when you do in fact have the concrete type and want to call an explicit interface method. I deal with this in one of two ways:
- Add publics and have the interface methods forward to them for the implementation. Typically happens with simpler interfaces when working internally.
- (My preferred method) Add a
public IMyInterface I { get { return this; } }
(which should get inlined) and callfoo.I.InterfaceMethod()
. If multiple interfaces that need this ability, expand the name beyond I (in my experience it's rare that I have this need).
If you implement explicitly, you will only be able to reference the interface members through a reference that is of the type of the interface. A reference that is the type of the implementing class will not expose those interface members.
If your implementing class is not public, except for the method used to create the class (which could be a factory or IoC container), and except for the interface methods (of course), then I don't see any advantage to explicitly implementing interfaces.
Otherwise, explicitly implementing interfaces makes sure that references to your concrete implementing class are not used, allowing you to change that implementation at a later time. "Makes sure", I suppose, is the "advantage". A well-factored implementation can accomplish this without explicit implementation.
The disadvantage, in my opinion, is that you will find yourself casting types to/from the interface in the implementation code that does have access to non-public members.
Like many things, the advantage is the disadvantage (and vice-versa). Explicitly implementing interfaces will ensure that your concrete class implementation code is not exposed.
An implicit interface implementation is where you have a method with the same signature of the interface.
An explicit interface implementation is where you explicitly declare which interface the method belongs to.
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: implicit and explicit interface implementations
Every class member that implements an interface exports a declaration which is semantically similar to the way VB.NET interface declarations are written, e.g.
Public Overridable Function Foo() As Integer Implements IFoo.Foo
Although the name of the class member will often match that of the interface member, and the class member will often be public, neither of those things is required. One may also declare:
Protected Overridable Function IFoo_Foo() As Integer Implements IFoo.Foo
In which case the class and its derivatives would be allowed to access a class member using the name IFoo_Foo
, but the outside world would only be able to access that particular member by casting to IFoo
. Such an approach is often good in cases where an interface method will have specified behavior on all implementations, but useful behavior on only some [e.g. the specified behavior for a read-only collection's IList<T>.Add
method is to throw NotSupportedException
]. Unfortunately, the only proper way to implement the interface in C# is:
int IFoo.Foo() { return IFoo_Foo(); }
protected virtual int IFoo_Foo() { ... real code goes here ... }
Not as nice.
One important use of explicit interface implementation is when in need to implement interfaces with mixed visibility.
The problem and solution are well explained in the article C# Internal Interface.
For example, if you want to protect leakage of objects between application layers, this technique allows you to specify different visibility of members that could cause the leakage.
'Program Tip' 카테고리의 다른 글
이론적으로 사이클 당 최대 4 개의 FLOP을 달성하려면 어떻게해야합니까? (0) | 2020.10.02 |
---|---|
부트 스트래핑이란 무엇입니까? (0) | 2020.10.02 |
수정 된 커밋을 원격 Git 저장소에 푸시하려면 어떻게해야합니까? (0) | 2020.10.02 |
Swift 기반 애플리케이션은 OS X 10.9 / iOS 7 이하에서 작동합니까? (0) | 2020.10.02 |
Cygwin과 MinGW의 차이점은 무엇입니까? (0) | 2020.10.02 |