Preface: You probably do not need to implement a generic "Cast" method. Be careful when implementing generic methods in C# for performance to avoid boxing (avoid using object
). Additionally if you hide the actual explicit cast operator in a generic method then the C# compiler can't give the consuming code early warning that a cast is provably incorrect.
Consider:
Int32 i = new Int32();
String s = (String)o; // <-- CS0030 Cannot convert type 'int' to 'string'
But with your code:
Int32 i = new Int32();
String s1 = To1<String>( i ); // No compiler error, despite incompatible types.
String s2 = To2<String>( i ); // Also no compiler error.
As for your questions:
Are the two implementations not equivalent?
No. See what happens when you pass val: null
:
// Using your functions `To1` and `To2`:
String strFromObj = To1<String>( new Object() ); // throws InvalidCastException (thrown from your code)
String strFromNull = To1<String>( null ); // throws InvalidCastException (thrown from your code)
String strFromObj = To2<String>( new Object() ); // throws InvalidCastException (thrown from the operator)
String strFromNull = To2<String>( null ); // returns null;
Why does the first function compile and the second emit a warning?
- Your first function uses the
is
operator, which is safe and will never throw (note that it is your code which is throwing InvalidCastException
in this case).
- Your second function blindly uses a cast expression without doing a type-check first so it's unsafe because the operator itself throws
InvalidCastException
if T
is incorrect, unless the operand is null
.
- The
foo is T bar
operator tests the type of non-null
values. If foo
is null
then the is
expression returns false and bar
is default(T)
.
- The
T bar = (T)foo
cast expression will cast null
values but only if T
is a reference type - but this syntax may also invoke a custom explicit conversion operator instead of performing a cast.
Also note that C# 8.0's nullable-reference-types feature (object?
) is not enforced at runtime: the compiler does not add throw new ArgumentNullException
statements to your compiled code, so if you change your code to use object
instead of object?
you should still add if( val is null ) throw new ArgumentNullException( nameof(val) );
.