首页 文章

C#接口 . 隐式实现与显式实现

提问于
浏览
581

在C#中实现接口 implicitlyexplicitly 有什么区别?

什么时候应该使用隐式?什么时候应该使用显式?

一个或那个有任何优点和/或缺点吗?


微软的官方指南(来自第一版Framework Design Guidelines)声明 using explicit implementations are not recommended ,因为它给代码带来了意想不到的行为 .

我认为这个指南非常 valid in a pre-IoC-time ,当你不传递接口时 .

任何人都可以触及这方面吗?

11 回答

  • 452

    除了已经提供的优秀答案之外,还有一些情况需要显式实现,以便编译器能够找出所需的内容 . 看一下 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 ,因此我们也需要 . 但是,请继续使用泛型和普通版本 both implement functions with the same method signature (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:IEnumerable的显式定义中的小部分间接作用,因为在函数内部,编译器知道变量的实际类型是StringList,这就是它如何解析函数调用 . 实现某些抽象层的一些微不足道的事实似乎已经积累了一些.NET核心接口 .

  • 12

    隐式接口实现是指具有相同签名的接口的方法 .

    显式接口实现是您显式声明方法所属的接口的位置 .

    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

  • 183

    显式接口实现的一个重要用途是在需要使用 mixed visibility 实现接口时 .

    问题和解决方案在文章C# Internal Interface中得到了很好的解释 .

    例如,如果要保护应用程序层之间的对象泄漏,则此技术允许您指定可能导致泄漏的成员的不同可见性 .

  • 6

    我大部分时间都使用显式接口实现 . 以下是主要原因 .

    Refactoring is safer

    更改接口时,编译器可以检查它是否更好 . 隐式实现更难 .

    我想到两个常见的情况:

    • 向接口添加一个函数,其中实现此接口的现有类恰好具有与新接口具有相同签名的方法 . 这可能导致意外行为,并且几次困扰我 . 调试时很难"see",因为该函数很可能与文件中的其他接口方法无关(下面提到的自我记录问题) .

    • 从界面中删除功能 . 隐式实现的方法将突然死代码,但显式实现的方法将被编译错误捕获 . 即使死代码保持良好,我也希望被强制审查并推广它 .

    不幸的是,C#没有强制我们将方法标记为隐式实现的关键字,因此编译器可以进行额外的检查 . 由于需要使用'override'和'new',虚方法没有上述任何一个问题 .

    注意:对于固定或很少更改的接口(通常来自供应商API),这不是问题 . 但是对于我自己的界面,我无法预测它们何时/如何改变 .

    It's self-documenting

    如果我在一个类中看到'public bool Execute()',它将需要额外的工作来弄清楚它是一个接口的一部分 . 有人可能不得不评论它这样说,或者把它放在一组其他接口实现中,所有这些都在一个区域或分组评论中说“ITask的实现” . 当然,这仅在组头不在屏幕外时才有效 .

    鉴于:'bool ITask.Execute()'清晰明确 .

    Clear separation of interface implementation

    我认为界面比公共方法更“公开”,因为它们被设计为只露出混凝土类型的一些表面区域 . 它们将类型简化为一种能力,一种行为,一组特征等 . 在实施中,我认为保持这种分离是有用的 .

    当我查看类的代码时,当我遇到显式接口实现时,我的大脑转变为“代码 Contract ”模式 . 这些实现通常只是转发到其他方法,但有时它们会进行额外的状态/参数检查,传入参数的转换以更好地匹配内部需求,甚至转换用于版本控制目的(即多代接口都归结为常见的实现) .

    (我意识到公共也是代码 Contract ,但接口更强大,特别是在接口驱动的代码库中,直接使用具体类型通常是仅限内部代码 . )

    相关:Reason 2 above by Jon .

    And so on

    加上其他答案中已经提到的优点:

    问题

    这不是一切乐趣和快乐 . 在某些情况下,我坚持使用implicits:

    • 值类型,因为这将需要装箱和降低perf . 这不是't a strict rule, and depends on the interface and how it'的意图 . IComparable的?隐 . IFormattable?可能是显而易见的 .

    • 具有经常直接调用的方法的简单系统接口(如IDisposable.Dispose) .

    另外,当你确实具有具体类型并且想要调用显式接口方法时,执行转换可能会很痛苦 . 我用以下两种方式之一处理这个问题:

    • 添加公共并将接口方法转发给它们以实现 . 通常在内部工作时使用更简单的接口 .

    • (我的首选方法)添加 public IMyInterface I { get { return this; } } (应该内联)并调用 foo.I.InterfaceMethod() . 如果需要此功能的多个接口,请将名称扩展到I之外(根据我的经验,我很少有这种需求) .

  • 31

    如果明确实现,则只能通过接口类型的引用引用接口成员 . 作为实现类类型的引用不会公开这些接口成员 .

    如果你的实现类不是公共的,除了用于创建类的方法(可以是工厂或IoC容器),除了接口方法(当然),那么我没有看到显式实现的任何优势接口 .

    否则,显式实现接口可确保不使用对具体实现类的引用,从而允许您稍后更改该实现 . 我认为,“确定”是“优势” . 一个精心设计的实现可以在没有明确实现的情况下实现此目

    在我看来,缺点是你会发现自己在实现代码中向接口或从接口转换类型,这些接口可以访问非公共成员 .

    像许多事情一样,优点是缺点(反之亦然) . 显式实现接口将确保不暴露您的具体类实现代码 .

  • 30

    隐式定义只是将接口所需的方法/属性等直接添加到类作为公共方法 .

    显式定义仅在您直接使用接口而不是底层实现时强制公开成员 . 在大多数情况下这是优选的 .

    • 通过直接使用接口,您无法确认,并将代码耦合到底层实现 .

    • 如果您的代码中已经有一个公共属性Name,并且您想要实现一个也具有Name属性的接口,那么明确地执行它将使两者分开 . 即使他们做同样的事情,我仍然会将显式调用委托给Name属性 . 您永远不会知道,您可能想要更改Name对普通类的工作方式以及Name,接口属性如何在以后工作 .

    • 如果你隐式实现了一个接口,那么你的类现在暴露出可能只与接口客户端相关的新行为,这意味着你没有足够简洁地保持你的类(我的观点) .

  • 17

    原因#1

    当我想阻止"programming to an implementation"(Design Principles from Design Patterns)时,我倾向于使用显式接口实现 .

    例如,在基于_107072的Web应用程序中:

    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

    我可能采用显式接口实现的另一个原因是保持类的"default"接口更清晰 . 例如,如果我正在开发ASP.NET服务器控件,我可能需要两个接口:

    • 该类的主要接口,由网页开发人员使用;和

    • 我开发的用于处理控件逻辑的演示者使用的"hidden"接口

    一个简单的例子如下 . 这是一个列出客户的组合框控件 . 在此示例中,网页开发人员对填充列表不感兴趣;相反,他们只是希望能够通过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; }
    }
    

    演示者填充数据源,网页开发人员永远不需要知道它的存在 .

    但它不是一个银色的炮弹

    我不建议总是使用显式接口实现 . 这只是他们可能的两个例子很有帮助 .

  • 8

    通过C#从CLR引用Jeffrey Richter
    EIMI 表示 E xplicit I nterface M ethod I mplementation)

    了解使用EIMI时存在的某些后果至关重要 . 由于这些后果,你应尽量避免使用EIMI . 幸运的是,通用接口可以帮助您避免使用EIMI . 但有时您可能需要使用它们(例如实现两个具有相同名称和签名的接口方法) . 以下是EIMI的主要问题:没有文档说明类型如何专门实现EIMI方法,并且没有Microsoft Visual Studio IntelliSense支持 . 在转换为接口时,值类型实例被装箱 . 派生类型无法调用EIMI .

    如果使用接口引用ANY虚拟链可以在任何派生类上显式替换为EIMI,并且当将此类型的对象强制转换为接口时,将忽略您的虚拟链并调用显式实现 . 这不过是多态性 .

    EIMI还可用于隐藏非强类型接口成员从基本框架接口的实现,如IEnumerable <T>,因此您的类不直接暴露非强类型方法,但语法正确 .

  • 5

    实现接口的每个类成员都导出一个声明,该声明在语义上类似于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 方法的指定行为是抛出 NotSupportedException ] . 不幸的是,在C#中实现接口的唯一正确方法是:

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

    不太好 .

  • 1

    Implicit 是您通过 class 成员定义界面的时候 . Explicit 是在界面上定义类中的方法时 . 我知道这听起来令人困惑,但这就是我的意思: 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.
    

    我主要使用explicit来保持实现干净,或者当我需要两个实现时 . 但不管我很少使用它 .

    我确信有更多的理由使用它/不使用它其他人会发布 .

    请参阅此主题中的next post,了解每个主题背后的理由 .

  • 64

    除了已经说明的其他原因之外,这是一个类正在实现两个具有相同名称和签名的属性/方法的不同接口的情况 .

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

    此代码编译并运行正常,但Title属性是共享的 .

    显然,我们希望Title返回的值取决于我们是将Class1视为Book还是Person . 这是我们可以使用显式接口的时候 .

    string IBook.Title
    {
        get
        {
            return "The Hitchhikers Guide to the Galaxy";
        }
    }
    
    string IPerson.Title
    {
        get
        {
            return "Mr";
        }
    }
    
    public string Title
    {
        get { return "Still shared"; }
    }
    

    请注意,显式接口定义被推断为Public - 因此您无法将它们声明为公开(或其他)显式 .

    另请注意,您仍然可以拥有“共享”版本(如上所示),但尽管这是可能的,但这种属性的存在是值得怀疑的 . 也许它可以用作Title的默认实现 - 这样就不必修改现有代码就可以将Class1强制转换为IBook或IPerson .

    如果未定义"shared"(隐式) Headers ,则Class1 must 的使用者首先将Class1的实例显式转换为IBook或IPerson,否则代码将无法编译 .

相关问题