首页 > 解决方案 > 为什么 List 使用显式接口方法实现来实现非泛型接口方法?

问题描述

// Some interface method signature

public interface IList : ICollection {
   ...
   bool Contains(Object value);
   ...
}

public interface IList<T> : ICollection<T> { ... }

public interface ICollection<T> : IEnumerable<T> {
   ...
   bool Contains(T item);
   ...
}

下面是源代码Listhttps ://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs

public class List<T> : IList<T>, System.Collections.IList, IReadOnlyList<T> {
   ...
   public bool Contains(T item) {
      ...
   }

   bool System.Collections.IList.Contains(Object item) {
      ...
   }
}

您可以看到List使用显式接口方法实现(显式指定接口的名称)来实现非泛型Contains。但是我对显式接口方法实现的了解是,只有当有两个具有相同签名的接口方法时才这样做。

但是对于public bool Contains(T item)and public bool Contains(Object item),它们是不同的方法,因为它们具有不同的签名(通用参数和非通用参数),因此List将被实现为:

public class List<T> : IList<T>, System.Collections.IList, IReadOnlyList<T> {
   public bool Contains(T item) { ... }

   public bool Contains(Object item) { ... }
}

那为什么 List 使用显式接口方法实现来实现非泛型接口方法呢?在这种情况下,我看不出使用显式接口方法实现有什么好处。我在这里错过了什么吗?

标签: c#.netexplicit-interface

解决方案


可以看到List使用显式接口方法实现(显式指定接口的名称)来实现非泛型Contains

确实。

但那是因为IList接口(不是IList<T>)已有数十年的历史——从原始、黑暗的时代开始,在 .NET 支持泛型之前(.NET 1.0 于 2001 年问世——直到 2005 年的 .NET Framework 2.0 才添加泛型) . 那真是一个被上帝遗忘的时代。

List<T>(但不是IList<T>)实现IList接口,以便新的泛型List<T>可以由接受的旧代码使用IList(以允许保留对象身份且无需分配单独IList实例的方式)。

假设现在是 2005 年,并且您正在编写一些奇妙的 C# 2.0 代码,这些代码使用了花哨的新的具体化泛型,并且List<T>- 但您需要与上次更新于 2004 年的库进行交互:

public void YourNewSexyGenericCode()
{
    List<String> listOfString = new List<String>() { "a", "b", "c" };
    
    OldAndBustedNET10CodeFrom2004( listOfString ); // <-- This works because List<String> implements IList.

    foreach( String s in listOfString ) Console.WriteLine( s );
}

public void OldAndBustedNET10CodeFrom2004( IList listOfString )
{
    listOfString.Add( "foo" );
    listOfString.Add( "bar" );
    listOfString.Add( "baz" );
    return;
}

如果List<T>没有实现IList,那么你将不得不做这样可怕的事情:

    List<String> listOfString = new List<String>() { "a", "b", "c" };
    
    // Step 1: Create a new separate IList and copy everything from your generic list into it.
    IList classicList = new ArrayList();
    classicList.AddRange( listOfString );

    // Step 2: Pass the IList in:
    OldAndBustedNET10CodeFrom2004( classicList );

    // Step 3: Copy the elements back, in a type-safe manner:
    foreach( Object maybeString in classicList )
    {
        String asString = maybeString as String; // The `is String str` syntax wasn't available back in C# 2.0
        if( asString != null )
        {
            listOfString.Add( asString );
        }
    }

    // Step 4: Continue:
    foreach( String s in listOfString ) Console.WriteLine( s );

但是我对显式接口方法实现的了解是,只有当有两个具有相同签名的接口方法时才这样做。

你误会了。除了实现冲突的接口(例如隐藏接口的实现、出于兼容性原因(如)和美学原因(减少 API 混乱、虽然应该用于此)。internalIListEditorBrowsable

但是对于public bool Contains(T item)and public bool Contains(Object item),它们是不同的方法,因为它们具有不同的签名(通用参数和非通用参数),因此List将被实现为...

在上面的段落中,我注意到这IList是一个类型论不健全的接口,即:它允许你做无意义和/或有害的事情,例如:

List<String> listOfString = new List<String>() { "a", "b", "c" };
IList asIList = listOfString;
asIList.Add( new Person() );

编译器会让这种情况发生,但它会在运行时崩溃,因为 aList<String>不能包含Person. 由于 .NET 支持协变和逆变(这就是为什么您可以安全地将 any 隐式转换为, 因为, 尽管实际上并没有实现,但它确实实现了),这可以通过具体化泛型解决。List<String>IEnumerable<Object>String : ObjectList<T>IEnumerable<Object>IEnumerable<T>

那为什么 List 使用显式接口方法实现来实现非泛型接口方法呢?

因为IList这是一个糟糕的界面,今天没有人应该使用它,但是由于遗留兼容性要求,有些人被迫/被迫使用它。每个人都希望看到这些遗留接口消失(尤其是我自己),但我们不能,因为它会破坏二进制应用程序的兼容性,这对于任何运行时或平台在 SWE 生态系统中生存都是必不可少的(这也是 .NET 团队不幸拥有的原因拒绝许多有意义但会破坏现有编译程序的频繁请求(例如IList<T>扩展IReadOnlyList<T>)。

在这种情况下,我看不出使用显式接口方法实现有什么好处。我在这里错过了什么吗?

你错过了一些东西 - 我希望我的回答能照亮你的想法。


推荐阅读