Reputation: 3016
Why does the compiler ask for an explicit cast for a generic - when an implicit one has been defined - only when the generic has been instantiated with an interface?
class G<T>
{
public G(T unused) {}
public static implicit operator G<T>(T unused) => new G<T>(unused);
}
interface I { }
class C : I { }
class Example
{
void Main()
{
G<int> xint1 = 1; // OK
G<int?> xint2 = 1; // OK
G<object> xobject1 = new object(); // OK
G<object?> xobject2 = new object(); // OK
C c = new C();
G<C> gc1 = c; // OK
G<C?> gc2 = c; // OK
I i = c;
G<I> g1 = i;
// ^-- error CS0266: Cannot implicitly convert type 'I' to
// 'G<I>'. An explicit conversion exists (are you missing a
// cast?)
G<I?> g2 = i;
// ^-- error CS0266: Cannot implicitly convert type 'I' to
// 'G<I?>'. An explicit conversion exists (are you missing a
// cast?)
G<I?> g3 = (I?)i;
// ^-- error CS0266: Cannot implicitly convert type 'I' to
// 'G<I?>'. An explicit conversion exists (are you missing a
// cast?)
// ^-- warning CS8600: Converting null literal or possible null
// value to non-nullable type.
G<I?> g4 = (I)i;
// ^-- error CS0266: Cannot implicitly convert type 'I' to
// 'G<I?>'. An explicit conversion exists (are you missing a
// cast?)
G<I> g5 = (G<I>)i; // OK
G<I?> g6 = (G<I?>)i; // OK
}
}
Upvotes: 3
Views: 133
Reputation: 4855
That's due to the limitations of user-defined conversions:
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:
- S₀ and T₀ are different types.
- Either S₀ or T₀ is the class or struct type in which the operator declaration takes place.
- Neither S₀ nor T₀ is an interface_type.
- Excluding user-defined conversions, a conversion does not exist from S to T or from T to S.
So, when the compiler sees an I
variable on the right side of an assignment to a G<I>
variable, it's not looking for user-defined conversion methods and issues a standard error that we need an explicit cast as it would normally from interface value
to someclass-value
.
In non-generic cases (or closed-generic G<I>
like the one below), we'd get a compile-time error for the method declaration
//CS0552 'G<T>.implicit operator G<I>(I)': user-defined conversions to or from an interface are not allowed
public static implicit operator G<I>(I unused) => new G<I>(unused);
But in our open-generic case where we have an unknown T
, it was apparently decided to not even issue a warning that when T
is I
, the conversion won't be possible/considered by the compiler.
EDIT: A bit of a deeper dive into our generic case reveals some ... fun facts:
The compiler doesn't honor its own rule when it comes to generics for method generation. To demonstrate we can do this:
var implicitMethod = typeof(G<I>).GetMethod("op_Implicit");
implicitMethod.ReturnType.Dump(); // G`1[I]
implicitMethod.GetParameters()[0].ParameterType.Dump(); // I
C c = new C();
G<I> surprise = c; // OK?!
IL for the last two statements:
IL_0018: newobj instance void C::.ctor() /* 06000008 */
IL_001d: call class G`1<!0> class G`1<class I>::op_Implicit(!0) /* 0A00000E */
It looks like we do have this method in the metadata:
public static implicit operator G<I>(I unused) => new G<I>(unused);
and we can "call it" from our code by having the implicit conversion.
Restriction is that we can't pass it direct references of I
, but references of classes that implement I
.
EDIT 2:
We can, however, hack it (almost?) completely if we do our implicit assignment in a generic method:
G<T> ReturnGI<T>(T instance) {
G<T> result = instance;
return result;
}
I i = new C();
var gi = ReturnGI(i); // will be G<I>
Upvotes: 7