首页 > 解决方案 > `is` 运算符和转换为泛型类型有什么区别?

问题描述

为什么第一个函数编译而第二个函数发出警告?这两个实现不等效吗?的类型valTT两种实现中都有。

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
}

标签: c#nullable

解决方案


前言:你可能不需要实现一个通用的“Cast”方法。在 C# 中实现泛型方法以提高性能时要小心,以避免装箱(避免使用object)。此外,如果您在泛型方法中隐藏实际的显式转换运算符,则 C# 编译器无法提前警告消费代码,即转换可能是不正确的。

考虑:

Int32 i = new Int32();
String s = (String)o; // <-- CS0030 Cannot convert type 'int' to 'string'

但是使用您的代码:

Int32 i = new Int32();
String s1 = To1<String>( i ); // No compiler error, despite incompatible types.
String s2 = To2<String>( i ); // Also no compiler error.

至于你的问题:

这两个实现不等效吗?

不。看看你通过时会发生什么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;

为什么第一个函数编译而第二个函数发出警告?

  • 您的第一个函数使用is运算符,它是安全的并且永远不会抛出(请注意,在这种情况下抛出的是您的代码InvalidCastException)。
  • 您的第二个函数盲目地使用强制转换表达式而没有先进行类型检查,因此它是不安全的,因为运算符本身会抛出InvalidCastExceptionifT不正确,除非操作数是null.

  1. foo is T bar运算符测试null的类型。如果foonull,则is表达式返回 false 和baris default(T)
  2. T bar = (T)foo强制转换表达式将强制转换null,但前提T是它是引用类型 - 但此语法也可能调用自定义显式转换运算符而不是执行强制转换。

另请注意,C# 8.0 的 nullable-reference-types 功能 ( object?) 在运行时未强制执行:编译器不会将throw new ArgumentNullException语句添加到已编译的代码中,因此如果您更改代码以使用object而不是object?您仍应添加if( val is null ) throw new ArgumentNullException( nameof(val) );.


推荐阅读