【问题标题】:Why explicit interface implementation works that way? [duplicate]为什么显式接口实现以这种方式工作? [复制]
【发布时间】:2019-11-28 16:16:29
【问题描述】:

为什么这个程序打印“子名”而不是“基本名”?

using System;

class Program
{
    static void Main(string[] args)
    {
        var child = new Child();
        Console.WriteLine(((INamedObject)child).Name);
        Console.ReadLine();
    }
}

interface INamedObject
{
    string Name { get; }
}

class Base : INamedObject
{
    string INamedObject.Name
    {
       get
       {
         return "Base Name";
       }
    }
}

class Child : Base, INamedObject
{
   public string Name
   {
      get
      {
         return "Child Name";
      }
   }
}

我希望当我将 child 转换为 INamedObject 时,将调用 INamedObject 显式实现的 Name 属性。但是发生的情况是调用了 Child.Name 属性。

为什么当我这样声明 Child 类时(从 Child 中删除 INamedObject):

class Child : Base
{
   public string Name
   {
      get
      {
         return "Child Name";
      }
   }
}

它开始打印“基本名称”?

【问题讨论】:

  • 您有两个 INamedObject 实现。当您将对象转换为接口时,您将获得最派生的实现。
  • 子类有 两个 的 INamedObject.Name 实现,您总是希望避免这种情况。但它不会混淆 CLR,它会在运行时知道当它绑定接口时 Child 具有更直接的匹配而不是继承。有时您必须使用显式接口实现,当一个类实现碰巧具有相同成员名称的多个接口时,它是为了解决歧义,这不是一个例子。

标签: c# .net interface


【解决方案1】:

其他答案没有正确识别您偶然发现的 C# 功能。

您发现了 C# 的一个有点令人困惑的特性,称为“接口重新实现”。规则是,当派生类明确地重新声明已由基类实现的接口时,编译器重新开始并从头开始重新进行接口映射

如果发生这种情况,则派生类型较多的方法优先于派生类型较少的方法,因为我们假设开发更多派生类型的开发人员具有更好 实现比开发基类版本的开发者。毕竟,如果派生版本更糟糕,开发者就不会实现它!

此规则允许您决定是否希望派生类替换基类接口映射,因为有时您想要,有时您不想。

有关此功能的更多详细信息,请参阅我 2011 年的文章:

https://blogs.msdn.microsoft.com/ericlippert/2011/12/08/so-many-interfaces-part-two/

您可能还会发现此答案很有帮助:

Abstract base classes that implement an interface

有关描述此语言功能的规范部分,请参阅

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/interfaces#interface-re-implementation

【讨论】:

    【解决方案2】:

    希望这不会增加您的困惑,但有助于了解成员的基本选择机制:

    using System;
    
    public static class Program
    {
        public static void Main(string[] args)
        {
            var child = new Child();
    
            PrintNatural(child);
            Console.WriteLine();
    
            PrintAsINamedObject(child);
            Console.WriteLine();
    
            PrintAsINamedAndAgedObject(child);
            Console.WriteLine();
    
            PrintAsBoth(child);
            Console.WriteLine();
    
            Console.ReadLine();
        }
    
        private static void PrintNatural(Child someChild) {
            Console.WriteLine("Natural Name: " + someChild.Name);
            Console.WriteLine("Natural Age: " + someChild.Age);
        }
    
        private static void PrintAsINamedObject(INamedObject someChild) {
            Console.WriteLine("Name as INamedObject: " + someChild.Name);
        }
    
        private static void PrintAsINamedAndAgedObject(INamedAndAgedObject someChild) {
            Console.WriteLine("Name as INamedAndAgedObject: " + someChild.Name);
            Console.WriteLine("Age as INamedAndAgedObject: " + someChild.Age);
        }
    
        private static void PrintAsBoth<T>(T instance) where T: INamedObject, INamedAndAgedObject
        {
            //Console.WriteLine("Name as Generic: " + instance.Name); does not compile Name is ambiguous to the compiler
            Console.WriteLine("Name as Generic<INamedObject>: " + ((INamedObject)instance).Name);
            Console.WriteLine("Name as Generic<INamedAndAgedObject>: " + ((INamedAndAgedObject)instance).Name);
            Console.WriteLine("Age as Generic: " + instance.Age);
        }
    }
    
    interface INamedObject
    {
        string Name { get; }
    }
    
    interface INamedAndAgedObject
    {
       string Name { get; }
       int Age { get; }
    }
    
    internal class Base : INamedObject
    {
        string INamedObject.Name => "Base Name";
    }
    
    internal class Child : Base, INamedObject, INamedAndAgedObject
    {
       public string Name => "Child Name";
       public int Age => 1024;
    
       string INamedObject.Name => "Child Name Explicit (INamedObject)";
    
       string INamedAndAgedObject.Name => "Child Name Explicit (INamedAndAgedObject)";
       int INamedAndAgedObject.Age => 2048;
    }
    

    输出

    Natural Name: Child Name
    Natural Age: 1024
    
    Name as INamedObject: Child Name
    
    Name as INamedAndAgedObject: Child Name Explicit (INamedAndAgedObject)
    Age as INamedAndAgedObject: 2048
    
    Name as Generic<INamedObject>: Child Name
    Name as Generic<INamedAndAgedObject>: Child Name Explicit (INamedAndAgedObject)
    Age as Generic: 2048
    

    运行时选择更具体的成员,当您将INamedObject 添加到Child 时,它会从Child 中选择原始Name 属性,但是当您删除它并使用类“作为INamedObject "(通过显式转换),选择基本实现。

    请注意,显式接口实现最好用在其他场景中,而不是继承(作为您的示例)。当您需要在单个具体类中实现这些时,显式接口实现背后的意图是消除具有成员冲突(相同签名)的接口之间的成员(方法、属性等)。详情请见Explicit Interface Implementation (C# Programming Guide)

    您可以在此处使用此代码示例:StackOverflowQ59092900

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-04-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-01
      • 2013-11-17
      • 2018-11-28
      相关资源
      最近更新 更多