2
// 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?

dimitar.bogdanov
  • 387
  • 2
  • 10
  • 4
    The members of explicitly implementing interfaces are only exposed via a reference that is typed as the explicitly implemented interface. It would be confusing and suboptimal for the generic collections to expose these members. The non generic interfaces are implemented for compatibility reasons. So one reason may be to avoid API clutter. More significantly, it would be undesirable for these collections which are strongly typed to unsafe methods which would allow things like adding an integer to a collection of strings – Aluan Haddad Feb 19 '21 at 23:43
  • @Leandro that doen't matter, no compile error, you can try and see it works. If T is object, then the non generic method will be chosen to be used by CLR –  Feb 19 '21 at 23:44
  • @AluanHaddad "avoid API clutter", that make senses. But I feel that the reason to use explicit interface methopd implementationshould be more than that –  Feb 19 '21 at 23:47
  • 3
    Yes. I don't think that's the only reason. If you think about it, the non generic interface methods allow erroneous or invalid code. `List ss = new(); ss.Add(new Button());`. Such code defeats the type safety which is one of the primary advantages to generic collections – Aluan Haddad Feb 19 '21 at 23:50
  • @AluanHaddad wow, thanks for your concise answer. Why didn't I think about it before :( –  Feb 19 '21 at 23:52
  • @AluanHaddad yes if you look at the IConvertable interface that has no generic counterpart(so it won't have the list.Add(new Button) issue. https://learn.microsoft.com/en-us/dotnet/api/system.iconvertible?view=net-5.0 it mentions that "The common language runtime also uses the IConvertible interface internally, in explicit interface implementations, to simplify the code used to support conversions in the Convert class and basic common language runtime types." but not sure what does it mean? EIMI doesn't make a thing easier, it only makes it more complicated, isn' t it –  Feb 19 '21 at 23:59
  • As you say, it's a different scenario since there isn't a generic counterpart, but from the wording I would say that they are saying that it simplifies implementing the BCL, not user code. However, that seems like an off-topic tangent – Aluan Haddad Feb 20 '21 at 00:05
  • @AluanHaddad OK, but that's still confusing because EIMI only restricts the usage that you have to cast it back to the interface varable, I can only see it makes everything harder, how does it make the implemention easier for BCL, do you have any idea? –  Feb 20 '21 at 00:08
  • 1
    Because it provides a common abstraction for convertible tights. Such things make it limitations easier in general. I would avoid casting to that interphase because as the documentation says many types will throw when calling various conversion methods. Remember that there were no generics in V1 – Aluan Haddad Feb 20 '21 at 00:13
  • I don't see how this is opinion based. I'm sure the design decision, whatever it was, is documented somewhere. – Aluan Haddad Feb 20 '21 at 03:01
  • @AluanHaddad Please tell me more about this "common abstraction for convertible tights" - I am very interested in applications of category-theory when it comes to practical fashion :) – Dai Feb 20 '21 at 04:40
  • 1
    @Dai Lol. It's a typo. I was talking into my phone and the speech to text messed up. I'm not sure if I should delete it because it's pretty funny – Aluan Haddad Feb 20 '21 at 05:32

1 Answers1

4

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.

Dai
  • 141,631
  • 28
  • 261
  • 374
  • Thanks for your answer. I am actualy more interested in why Int32 also uses EIMI for IConvertable interface –  Feb 20 '21 at 14:11
  • @amjad I think that would be better asked as a separate new question on SO. – Dai Feb 20 '21 at 14:52
  • Thank you for this detailed explanation. It has improved my understanding of generic interfaces. – Erik Midtskogen Mar 26 '22 at 15:32