This is based on a recent thread on the MSDN forums. Someone had code that looked like this:
public class CustomObject<T>
{
Object _obj;
public Object Value
{
get
{
return _obj;
}
}
public static explicit operator CustomObject<T>(T obj)
{
return new CustomObject<T>() { _obj = obj };
}
}
class App
{
static void Main()
{
var tmp1 = (CustomObject<Object>)true; // from bool
var tmp2 = (CustomObject<Object>)12; // from int
// The following code throws a System.InvalidCastException
// Unable to cast object of type 'System.Object' to
// type 'CustomObject`1[System.Object]'
var tmp3 = (CustomObject<Object>)new Object(); // from object
}
}
The code compiles fine but the third conversion results in an InvalidCastException
. And he was quite puzzled by it.
The answer is simple if you think about it, and the exception that’s thrown is a dead giveaway. When you have an object
there, the compiler sees that as a downcast (casting from base to a more derived type). The reason is that CusObject<T>
is derived from Object
(implicitly) and so when the type being converted is Object
, it will not call the explicit (or implicit) conversion operator, instead it will generate a castclass
IL instruction which will obviously fail.
To make that more clear, forget the “generics” and take this example:
class Base
{
}
class Derived : Base
{
public static explicit operator Derived(Base b)
{
return new Derived();
}
}
That will not compile and you’ll see this:
Error 1 ‘Derived.explicit operator Derived(Base)’: user-defined conversions to or from a base class are not allowed
Of course when you use a generic parameter, the compiler cannot anticipate that you’d try doing this, and so it will let you declare the operator. And when you later have an ambiguous situation where you cast from base to derived, it will simply ignore the explicit operator and instead do a downcast.
In the MSDN thread, Louis.fr pointed out the appropriate section in the language spec that talks about this. For those interested it’s 10.10.3. Quoting below:
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.