简体   繁体   中英

What is the difference between the `is` operator and a cast to a generic type?

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
}

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM