Search code examples
c#.netexplicit-interface

Why does List use an explicit interface method implementation to implement non-generic interface methods?


// 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);
   ...
}

Below is the source code of List: https://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) {
      ...
   }
}

You can see that List uses explicit interface method implementation(explicitly specify the interface's name) to implement the non-generic Contains. But what I know about explicit interface method implementation is, you only do it when there are two interface methods that have the same signature.

But for public bool Contains(T item) and public bool Contains(Object item), they are different methods because they have different signatures (generic parameter and non-generic parameter), so List would have been implemented as:

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

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

Then why does List use an explicit interface method implementation to implement non-generic interface methods? I cannot see any benefit to use explicit interface method implementation in this scenario. Am I missing something here?


Solution

  • You can see that List uses explicit interface method implementation (explicitly specify the interface's name) to implement the non-generic Contains

    Indeed.

    But that's because the IList interface (not IList<T>) is decades-old - from the primitive, dark, times before .NET had support for generics (.NET 1.0 came out in 2001 - generics weren't added until the .NET Framework 2.0 in 2005). It was a truly godforsaken time.

    List<T> (but not IList<T>) implements the IList interface so that the new generic List<T> could be consumed by older code that accepted an IList (in a way that allows preserving object identity and without requiring allocating a separate IList instance).

    Supposing it's 2005 and you're writing some wonderous C# 2.0 code that uses the fancy new reified generics and List<T> - but you need to interact with a library that was last updated in 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;
    }
    

    If List<T> didn't implement IList then you'd have to something horrible like this:

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

    But what I know about explicit interface method implementation is, you only do it when there are two interface methods that have the same signature.

    You are mistaken. There are many reasons to opt for explicit interface implementation besides implementing conflicting interfaces (such as hiding an implementation of an internal interface, implementing a type-theoretic unsound older/legacy interface for compatibility reasons (like IList), and for aesthetic reasons (reducing API clutter, though EditorBrowsable should be used for this).

    But for public bool Contains(T item) and public bool Contains(Object item), they are different methods because they have different signatures (generic parameter and non-generic parameter), so List would have been implemented as...

    In the paragraph above, I noted that IList is an type-theoretic unsound interface, that is: it allows you to do pointless and/or harmful things, for example:

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

    The compiler will let this happen but it will crash at runtime because a List<String> cannot contain a Person. This is solved with reified generics thanks to .NET's support for covariance and contravariance (this is why you can safely implicitly convert any List<String> to IEnumerable<Object>, because String : Object, even though List<T> does not actually implement IEnumerable<Object>, but it does implement IEnumerable<T>).

    Then why does List use an explicit interface method implementation to implement non-generic interface methods?

    Because IList is a terrible interface that no-one should be using today, but some people were/are forced into using it because of legacy compatibility requirements. Everyone wants to see these legacy interfaces disappear (especially myself) but we can't because it would break binary application compatibility, which is essential for any runtime or platform to survive in the SWE ecosystem (this is also why the .NET team unfortunately has to turn-down many frequent requests that make sense but would break existing compiled programs (such as making IList<T> extend IReadOnlyList<T>).

    I cannot see any benefit to use explicit interface method implementation in this scenario. Am I missing something here?

    You were missing something - I hope my answer illuminates your mind.