Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# generic implicit cast on Interface failed

Tags:

Why will the below not compile? What's special about the interface that causes the compiler to think it can't cast from Container<T> to T, when T is an interface? I don't think its a covariant issue, as I'm not downcasting, but perhaps it is. This is quite like Why C# compiler doesn't call implicit cast operator? but I don't think it's quite the same.

Product pIn =null; Product pOut; Container<Product> pContainer;  List<Product> pListIn = null; List<Product> pListOut; Container<List<Product>> pListContainer;  IList<Product> pIListIn = null; IList<Product> pIListOut; Container<IList<Product>> pIListContainer;  pContainer = pIn; pOut = pContainer; // all good  pListContainer = pListIn;  pListOut = pListContainer; // all good too  pIListContainer = pIListIn; // fails , cant do implicit cast for some reason pIListOut = pIListContainer; // and here too 

class Container<T> {  private T value;   private Container(T item) { value = item; }   public static implicit operator Container<T>(T item)  {   return new Container<T>(item);  }   public static implicit operator T(Container<T> container)  {   return container.value;  } } 

Cannot implicitly convert type 'Container<IList<Product>>' to 'IList<Product>'. An explicit conversion exists (are you missing a cast?) Cannot implicitly convert type 'IList<Product>' to 'Container<IList<Product>>'. An explicit conversion exists (are you missing a cast?) 
like image 829
jasper Avatar asked Nov 25 '10 20:11

jasper


1 Answers

User defined conversions aren't allowed on interfaces at all. It would potentially be ambiguous, because the type you're trying to convert from could implement the interface itself - at which point what would the cast mean? A reference conversion like a normal cast, or an invocation of the user-defined conversion?

From section 10.3.3 of the C# 4 spec:

For a given source type S and target type T, if S or T are nullable types, let S0 and T0 refer to their underlying types, otherwise S0 and T0 are equal to S and T respectively. A class or struct is permitted to declare a conversion from a source type S to a target type T only if all of the following are true:

  • S0 and T0 are different types.
  • Either S0 or T0 is the class or struct type in which the operator declaration takes place.
  • Neither S0 nor T0 is an interface-type.
  • Excluding user-defined conversions, a conversion does not exist from S to T or from T to S.

and then later:

However, it is possible to declare operators on generic types that, for particular type arguments, specify conversions that already exist as pre-defined conversions
...
In cases where a pre-defined conversion exists between two types, any user-defined conversions between those types are ignored. Specifically:

  • If a pre-defined implicit conversion (§6.1) exists from type S to type T, all user-defined conversions (implicit or explicit) from S to T are ignored.
  • If a pre-defined explicit conversion (§6.2) exists from type S to type T, any user-defined explicit conversions from S to T are ignored. Furthermore:
    • If T is an interface type, user-defined implicit conversions from S to T are ignored.
    • Otherwise, user-defined implicit conversions from S to T are still considered.

Note the first nested bullet here.

(I can thoroughly recommend getting hold of the spec by the way. It's available online in various versions and formats, but the hardcopy annotated edition is also a goldmine of little nuggets from the team and others. I should confess a certain bias here, as I'm one of the annotators - but ignoring my stuff, all the other annotations are well worth reading!)

like image 180
Jon Skeet Avatar answered Oct 04 '22 08:10

Jon Skeet