0

Why does the first function compile and the second emit a warning? Are the two implementations not equivalent? The type of valT is T in both implementations.

https://dotnetfiddle.net/XV5AXU

T To1<T>(object? val)
{
  if (!(val is T valT))
  {
    throw new InvalidCastException();
  }

  return valT;
}

T To2<T>(object? val)
{
  var valT = (T)val;

  return valT; // Possible null reference return
}
Dai
  • 141,631
  • 28
  • 261
  • 374
pensono
  • 336
  • 6
  • 17

1 Answers1

1

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.

  1. 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).
  2. 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) );.

Dai
  • 141,631
  • 28
  • 261
  • 374