为啥要显式实现接口?

Posted

技术标签:

【中文标题】为啥要显式实现接口?【英文标题】:Why implement interface explicitly?为什么要显式实现接口? 【发布时间】:2011-05-05 10:27:18 【问题描述】:

那么,明确实现接口的好用例到底是什么?

是否只是为了让使用该类的人不必查看智能感知中的所有这些方法/属性?

【问题讨论】:

【参考方案1】:

如果你实现了两个接口,都具有相同的方法和不同的实现,那么你必须显式地实现。

public interface IDoItFast

    void Go();

public interface IDoItSlow

    void Go();

public class JustDoIt : IDoItFast, IDoItSlow

    void IDoItFast.Go()
    
    

    void IDoItSlow.Go()
    
    

【讨论】:

是的,这正是 EIMI 解决的一种情况。 “Michael B”回答涵盖了其他要点。 很好的例子。喜欢接口/类名! :-) 我不喜欢它,在一个类中具有相同签名的两个方法做非常不同的事情?这是非常危险的事情,很可能会在任何大型开发中造成严重破坏。如果你有这样的代码,我会说你的分析和设计是 wahzoo。 @Mike 接口可能属于某个 API 或属于两个不同的 API。也许 love 在这里有点夸张,但我至少很高兴有明确的实现。 @BrianRogers 还有方法名 ;-)【参考方案2】:

当您有两个具有相同成员名称和签名的接口,但希望根据其使用方式更改其行为时,它也很有用。 (我不推荐这样写代码):

interface Cat

    string Name get;


interface Dog

    string Nameget;


public class Animal : Cat, Dog

    string Cat.Name
    
        get
        
            return "Cat";
        
    

    string Dog.Name
    
        get
        
            return "Dog";
        
    

static void Main(string[] args)

    Animal animal = new Animal();
    Cat cat = animal; //Note the use of the same instance of Animal. All we are doing is picking which interface implementation we want to use.
    Dog dog = animal;
    Console.WriteLine(cat.Name); //Prints Cat
    Console.WriteLine(dog.Name); //Prints Dog

【讨论】:

我见过的最奇怪的 OO 相关示例:public class Animal : Cat, Dog @mbx: 如果 Animal 也实现了 Parrot,那它将是一个 Polly-morphing 动物。 我记得一个卡通人物,一端是猫,另一端是狗;-) 80 年代有一部电视剧 .. “动物” .. 一个男人可以变身成一个.. 哦没关系 Morkies 有点像猫,也像忍者猫。【参考方案3】:

如果您有一个内部接口并且您不想公开实现类中的成员,则可以显式实现它们。隐式实现需要公开。

【讨论】:

好的,这就解释了为什么项目不能使用隐式实现进行编译。【参考方案4】:

它可以使公共接口更清晰以显式实现一个接口,即您的File 类可能显式实现IDisposable 并提供一个公共方法Close(),这对消费者来说可能比Dispose( 更有意义)。

F# only 提供显式接口实现,因此您始终必须强制转换为特定接口才能访问其功能,这使得接口的使用非常显式(没有双关语)。

【讨论】:

我认为大多数版本的VB也只支持显式接口定义。 @Gabe - 这比 VB 更微妙 - 实现接口的成员的命名和可访问性与表明它们是实现的一部分是分开的。因此,在 VB 中,查看@Iain 的答案(当前最佳答案),您可以分别使用公共成员“GoFast”和“GoSlow”实现 IDoItFast 和 IDoItSlow。 我不喜欢你的具体例子(恕我直言,唯一应该隐藏 Dispose 的是那些永远不需要清理的东西);一个更好的例子是 IList<T>.Add 的不可变集合的实现。 “你总是必须转换到特定的接口才能访问它的功能”。我不明白为什么:只要你编程到一个接口,而不是实现,使用你的对象的客户端不需要知道它收到了哪个特定的实现,也不需要转换它,而只是在接口方面使用它.【参考方案5】:

隐藏非首选成员很有用。例如,如果你同时实现了IComparable<T>IComparable,隐藏IComparable 重载通常会更好,以免给人留下可以比较不同类型对象的印象。同样,某些接口不符合 CLS,例如 IConvertible,因此如果您不明确实现接口,则需要符合 CLS 的语言的最终用户无法使用您的对象。 (如果 BCL 实现者没有隐藏原语的 IConvertible 成员,那将是非常灾难性的 :))

另一个有趣的注意事项是,通常使用这样的构造意味着显式实现接口的结构只能通过装箱到接口类型来调用它们。你可以通过使用通用约束来解决这个问题::

void SomeMethod<T>(T obj) where T:IConvertible

当你将一个 int 传递给它时,它不会装箱。

【讨论】:

您的约束中有错字。为了澄清,上面的代码确实有效。它需要在接口中方法签名的初始声明中。原帖没有说明这一点。此外,正确的格式是“void SomeMehtod(T obj) where T:IConvertible。注意,“)”和“where”之间有一个额外的冒号,不应该存在。仍然,+1 用于巧妙地使用泛型以避免昂贵的拳击。 嗨 Michael B. 那么为什么在 .NET 中实现字符串时有 IComparable 的公共实现: public int CompareTo(Object value) if (value == null) return 1; if (!(value is String)) throw new ArgumentException(Environment.GetResourceString("Arg_MustBeString")); return String.Compare(this,(String)value, StringComparison.CurrentCulture); 谢谢! string 出现在泛型之前,这种做法很流行。当 .net 2 出现时,他们不想破坏 string 的公共界面,因此他们将其保留在原样,并保留了安全措施。 @MichaelB 嗨,您能否再解释一下,如果您没有像 IConvertible 那样显式实现接口,为什么需要 CLS 合规性的语言的最终用户无法使用您的对象 @MichaelB msdn 文档说:公共语言运行时通常通过 Convert 类公开 IConvertible 接口。公共语言运行时也在内部使用 IConvertible 接口,在显式接口实现中,以简化用于支持 Convert 类和基本公共语言运行时类型中的转换的代码。”但我不确定它如何简化用于支持 Convert 类中的转换。【参考方案6】:

另一个有用的技术是让函数的方法的公共实现返回一个比接口中指定的值更具体的值。

例如,一个对象可以实现ICloneable,但仍然让其公开可见的Clone 方法返回它自己的类型。

同样,IAutomobileFactory 可能有一个返回AutomobileManufacture 方法,但实现IAutomobileFactoryFordExplorerFactory 可能有它的Manufacture 方法返回一个FordExplorer(它派生来自Automobile)。知道它具有FordExplorerFactory 的代码可以在FordExplorerFactory 返回的对象上使用FordExplorer 特定的属性,而无需进行类型转换,而只知道它具有某种类型的代码IAutomobileFactory 将简单地处理它以Automobile 的形式返回。

【讨论】:

+1...这将是我对显式接口实现的首选用法,尽管一个小的代码示例可能会比这个故事更清楚:)【参考方案7】:

显式实现接口的一些额外原因:

向后兼容性:如果ICloneable 接口发生变化,实现方法类成员不必更改其方法签名。

更简洁的代码:如果从 ICloneable 中删除 Clone 方法,则会出现编译器错误,但是如果您隐式实现该方法,您最终可能会得到未使用的“孤立”公共方法

强类型: 用一个例子来说明 supercat 的故事,这将是我首选的示例代码,当您直接将 Clone() 作为 MyObject 实例成员调用时,显式实现 ICloneable 允许强类型化 Clone()

public class MyObject : ICloneable

  public MyObject Clone()
  
    // my cloning logic;  
  

  object ICloneable.Clone()
  
    return this.Clone();
  

【讨论】:

对于那个,我更喜欢interface ICloneable&lt;out T&gt; T Clone(); T self get; 。请注意,在 T 上刻意没有ICloneable&lt;T&gt; 约束。虽然一个对象通常只能在其基可以被安全克隆的情况下才能被安全克隆,但人们可能希望从可以安全克隆的类的对象派生“可以”吨。为此,我建议不要让可继承类公开公共克隆方法。而是使用具有protected 克隆方法的可继承类和从它们派生并公开公共克隆的密封类。 当然会更好,除了 BCL 中没有 ICloneable 的协变版本,所以你必须创建一个吗? 所有 3 个示例都依赖于不太可能的情况并破坏接口最佳实践。【参考方案8】:

显式实现的另一个原因是可维护性

当一个类变得“忙碌”时——是的,它确实发生了,我们并不都有重构其他团队成员的代码的奢侈——然后有一个显式的实现可以清楚地表明一个方法可以满足一个接口契约。

因此它提高了代码的“可读性”。

【讨论】:

恕我直言,决定类是否应该向其客户公开方法更为重要。这决定了是显式的还是隐式的。记录几个方法属于一起,在这种情况下,因为它们满足合同 - 这就是 #region 的用途,并带有适当的标题字符串。以及对该方法的评论。【参考方案9】:

我们可以这样创建显式接口: 如果我们有 2 个接口,并且两个接口都有相同的方法,并且一个类继承了这两个接口,那么当我们调用一个接口方法时,编译器会混淆要调用哪个方法,所以我们可以使用显式接口来解决这个问题。 这是我在下面给出的一个例子。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace oops3

    interface I5
    
        void getdata();    
    
    interface I6
    
        void getdata();    
    

    class MyClass:I5,I6
    
        void I5.getdata()
        
           Console.WriteLine("I5 getdata called");
        
        void I6.getdata()
        
            Console.WriteLine("I6 getdata called");
        
        static void Main(string[] args)
        
            MyClass obj = new MyClass();
            ((I5)obj).getdata();                     

            Console.ReadLine();    
        
    

【讨论】:

【参考方案10】:

在显式定义的接口的情况下,所有方法都是自动私有的,您不能将访问修饰符公开给它们。假设:

interface Iphone

   void Money();



interface Ipen

   void Price();



class Demo : Iphone, Ipen

  void Iphone.Money()    //it is private you can't give public               

      Console.WriteLine("You have no money");
  

  void Ipen.Price()    //it is private you can't give public

      Console.WriteLine("You have to paid 3$");
  




// So you have to cast to call the method


    class Program
    
        static void Main(string[] args)
        
            Demo d = new Demo();

            Iphone i1 = (Iphone)d;

            i1.Money();

            ((Ipen)i1).Price();

            Console.ReadKey();
        
    

  // You can't call methods by direct class object

【讨论】:

“所有方法都是自动私有的”——这在技术上是不正确的,b/c 如果它们实际上是私有的,那么无论是否强制转换,它们都根本无法调用。【参考方案11】:

System.Collections.Immutable 给出了一个不同的示例,其中作者选择使用该技术为集合类型保留熟悉的 API,同时删除接口中对其新类型没有意义的部分。

具体来说,ImmutableList&lt;T&gt; 实现了IList&lt;T&gt;,因此实现了ICollection&lt;T&gt;(in order 允许ImmutableList&lt;T&gt; 更容易与遗留代码一起使用),但是void ICollection&lt;T&gt;.Add(T item)ImmutableList&lt;T&gt; 没有意义:因为向不可变列表添加元素不得更改现有列表,ImmutableList&lt;T&gt; 也派生自 IImmutableList&lt;T&gt;,其 IImmutableList&lt;T&gt; Add(T item) 可用于不可变列表。

因此在Add 的情况下,ImmutableList&lt;T&gt; 中的实现最终看起来如下:

public ImmutableList<T> Add(T item)

    // Create a new list with the added item


IImmutableList<T> IImmutableList<T>.Add(T value) => this.Add(value);

void ICollection<T>.Add(T item) => throw new NotSupportedException();

int IList.Add(object value) => throw new NotSupportedException();

【讨论】:

以上是关于为啥要显式实现接口?的主要内容,如果未能解决你的问题,请参考以下文章

为啥要显式抛出 NullPointerException 而不是让它自然发生?

为啥接口的显式实现不能公开?

我怎样才能“显式”地快速实现一个协议?如果不可能,为啥?

为啥 Emit 中具有显式重载的接口实现对于公共和非公共的表现不同?

为什么要显式调用asyncio.StreamWriter.drain?

为啥在具有多个接口() 的对象中实现 QueryInterface() 时我需要显式向上转换