为什么要明确实现接口?


122

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

仅仅是为了使使用该类的人们不必查看intellisense中的所有那些方法/属性?

Answers:


146

如果您使用相同的方法和不同的实现来实现两个接口,则必须显式实现。

public interface IDoItFast
{
    void Go();
}
public interface IDoItSlow
{
    void Go();
}
public class JustDoIt : IDoItFast, IDoItSlow
{
    void IDoItFast.Go()
    {
    }

    void IDoItSlow.Go()
    {
    }
}

是的,这正是EIMI解决的一种情况。其他要点包含在“ Michael B”答案中。
加密

10
很好的例子。喜欢接口/类名!:-)
Brian Rogers

11
我不喜欢它,类中具有相同签名的两个方法做的事情大不相同?这是非常危险的东西,很可能在任何大型开发中造成破坏。如果您有这样的代码,我会说您的分析和设计工作已成定局。
米克

4
@Mike接口可能属于某个API或两个不同的API。也许爱情在这里有点夸张,但我至少很高兴可以使用显式实现。
TobiMcNamobi

@BrianRogers和方法名太;-)
Sнаđошƒаӽ

66

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

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

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

将int传递给int时,不会将其装箱。


1
您的约束中有错别字。为了清晰起见,上面的代码可以正常工作。它需要在接口中方法签名的初始声明中。原始帖子未指定。另外,正确的格式为“ void SomeMehtod <T>(T obj),其中T:IConvertible。请注意,“)”和“ where”之间不应有多余的冒号。仿制药以避免昂贵的拳击比赛
Zack Jannsen 2012年

1
嗨,迈克尔·B(Michael B.),您好。为什么在.NET中执行字符串时,会有IComparable的公共实现:} if(!(value是String)){抛出新的ArgumentException(Environment.GetResourceString(“ Arg_MustBeString”));; } return String.Compare(this,(String)value,StringComparison.CurrentCulture); } 谢谢!
zzfima 2015年

string在非专利药问世之前就已经流行了,这种做法很流行。当.net 2出现时,他们不想破坏.net 2的公共界面,string因此他们将其保留下来,并保留了适当的防护措施。
Michael B

37

明确实现接口的一些其他原因:

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

更清晰的代码:如果将Clone方法从IClo​​neable中删除,则会出现编译器错误,但是,如果隐式实现该方法,则可能会得到未使用的“孤立”公共方法

强类型化:为了举例说明超级猫的故事,这将是我的首选示例代码,当您直接将其作为实例成员调用时,ICloneable显式实现允许Clone()强类型化MyObject

public class MyObject : ICloneable
{
  public MyObject Clone()
  {
    // my cloning logic;  
  }

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

对于那个,我会更喜欢interface ICloneable<out T> { T Clone(); T self {get;} }。请注意,故意没有ICloneable<T>对T的限制。虽然通常只能在对象的基础可以安全地克隆对象的情况下,但可能希望从可以安全地克隆不能对象的对象的基类派生。为此,我建议不要让可继承的类公开公共克隆方法。而是具有带有protected克隆方法的可继承类和从它们派生并公开公共克隆的密封类。
supercat

当然,这会更好,但是BCL中没有ICloneable的协变版本,因此您必须创建一个吗?
Wiebe Tijsma

所有这三个示例都取决于不太可能的情况,并且破坏了界面最佳实践。
MickyD

13

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

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

同样,IAutomobileFactory可能有一个Manufacture方法返回a Automobile,而一个FordExplorerFactory实现IAutomobileFactory了的Manufacture方法可能有一个方法返回a FordExplorer(从派生Automobile)。知道它对a FordExplorerFactoryFordExplorer返回的对象具有可以使用特定属性的FordExplorerFactory代码,而不必进行类型转换,而只知道它具有某种类型的代码IAutomobileFactory将简单地将其返回处理为Automobile


2
+1 ...这将是我对显式接口实现的首选用法,尽管一个小的代码示例可能比这个故事更清楚:)
Wiebe Tijsma 2011年

7

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

interface Cat
{
    string Name {get;}
}

interface Dog
{
    string Name{get;}
}

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
}

60
我见过的最奇怪的OO相关示例:public class Animal : Cat, Dog
mbx 2012年

38
@mbx:如果Animal也实现了鹦鹉,那将是一个会变形的动物。
RenniePet 2013年

3
我记得一个卡通人物,它的一端是猫,而另一端是狗;-)
George Birbilis

2
上世纪80年代有一个电视连续剧..“动物” ..一个人可以变身为一个..哦,没关系
bkwdesign

Morkies有点像猫,也像忍者猫。
samis

6

它可以使公共接口更清洁以显式实现接口,即您的File类可以IDisposable显式实现并提供公共方法Close(),该方法对使用者比(Dispose()更有意义。

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


我认为大多数版本的VB也仅支持显式接口定义。
加布(Gabe)2010年

1
@Gabe-比VB更为微妙-实现接口的成员的命名和可访问性与表明它们是实现的一部分是分开的。因此,在VB中,查看@Iain的答案(当前最高答案),您可以分别使用公共成员“ GoFast”和“ GoSlow”来实现IDoItFast和IDoItSlow。
Damien_The_Unbeliever

2
我不喜欢您的特定示例(恕我直言,唯一应该隐藏的内容Dispose是永远不需要清除的内容);更好的示例是类似的不可变集合的实现IList<T>.Add
2013年

5

如果您有一个内部接口,并且不想公开地在类上实现成员,则可以显式实现它们。隐式实现必须公开。


好的,这说明了为什么项目无法使用隐式实现进行编译。
pauloya 2011年

4

明确实现的另一个原因是可维护性

当一个班级变得“忙碌”时(是的,是的,我们所有人都不具备重构其他团队成员代码的能力),然后通过一个显式的实现就清楚地知道那里有一种方法可以满足接口协定。

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


恕我直言,更重要的是确定类是否应该向其客户公开该方法。这决定了显性还是隐性。要记录几种方法属于一起的情况,在这种情况下是因为它们满足合同-就是这样#region,并带有适当的标题字符串。并对方法进行评论。
制造商史蒂夫(Steve)

1

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

具体地,ImmutableList<T>工具IList<T>并且因此ICollection<T>为了允许ImmutableList<T>将与传统代码更容易地使用),但void ICollection<T>.Add(T item)是没有意义为ImmutableList<T>:因为将一个元素增加到不可变列表必须不改变现有的列表,ImmutableList<T>还导出从IImmutableList<T>IImmutableList<T> Add(T item)可用于不可变的清单。

因此,在的情况下AddImmutableList<T>最终的实现如下所示:

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();

0

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

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

1
“所有方法都是自动私有的”-从技术上讲这是不正确的,如果实际上它们是私有的,则b / c根本无法调用,无论是否强制转换。
samis

0

这就是我们创建显式接口的方式: 如果我们有2个接口,并且两个接口都具有相同的方法,并且一个类继承了这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();    
        }
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.