关于.net:C#中用于类型转换的最佳实践是什么?

Which is the best practice in C# for type casting?

本问题已经有最佳答案,请猛点这里访问。

哪种方法是进行类型铸造和检查的最佳实践?

1
2
3
4
5
Employee e = o as Employee;
if(e != null)
{
 //DO stuff
}

1
2
3
4
5
if(o is Employee)
{
Employee e = (Employee) o;
 //DO stuff
}


至少有两种铸造的可能性,一种用于类型检查,二者的组合称为模式匹配。每个人都有自己的目的,这取决于情况:

硬石膏

1
var myObject = (MyType)source;

如果您完全确定给定对象是否属于该类型,则通常会这样做。如果您订阅了一个事件处理程序,并将发送方对象强制转换为正确的类型,那么就需要使用它。

1
2
3
4
5
6
7
private void OnButtonClick(object sender, EventArgs e)
{
    var button = (Button)sender;

    button.Text ="Disabled";
    button.Enabled = false;
}

软铸件

1
2
3
4
var myObject = source as MyType;

if (myObject != null)
    // Do Something

如果你不知道你是否真的有这种类型,这通常会被使用。因此,只需尝试强制转换它,如果不可能,只需返回一个空值。一个常见的例子是,如果只有在某些接口已满的情况下,您才需要做一些事情:

1
2
3
4
var disposable = source as IDisposable;

if(disposable != null)
    disposable.Dispose();

另外,as运算符不能用于struct上。这仅仅是因为操作员希望在强制转换失败时返回一个null,而一个struct永远不能是null

类型检查

1
var isMyType = source is MyType;

这很少被正确使用。只有当您只需要知道某个对象是否属于特定类型,但不必使用该对象时,此类型检查才有用。

1
2
3
4
if(source is MyType)
   DoSomething();
else
   DoSomethingElse();

模式匹配

1
2
if (source is MyType myType)
    DoSomething(myType);

模式匹配是dotnet框架中与强制转换相关的最新功能。但您也可以使用switch语句和when子句来处理更复杂的情况:

1
2
3
4
5
6
7
switch (source)
{
    case SpecialType s when s.SpecialValue > 5
        DoSomething(s);
    case AnotherType a when a.Foo =="Hello"
        SomethingElse(a);
}


我认为这是一个很好的问题,应该得到一个严肃而详细的答案。类型转换实际上是很多不同的东西。好的。

与C语言不同的是,C++语言对这些非常严格,所以我将把它命名为引用。我一直认为最好能理解事情是如何运作的,所以我将在这里为您详细介绍一下。下面是:好的。

动态铸造和静态铸造好的。

C具有值类型和引用类型。引用类型始终遵循继承链,从对象开始。好的。

基本上,如果你做(Foo)myObject,你实际上是在做一个动态的演员表,如果你做(object)myFoo,或者简单的object o = myFoo,你是在做一个静态的演员表。好的。

动态强制转换要求您执行类型检查,即运行时将检查要强制转换的对象是否属于该类型。毕竟,你要抛弃继承树,所以你最好完全抛弃其他东西。如果是这样的话,你最终会得到一个InvalidCastException。因此,动态强制转换需要运行时类型信息(例如,它需要运行时知道哪个对象具有什么类型)。好的。

静态强制转换不需要类型检查。在本例中,我们将在继承树中进行强制转换,因此我们已经知道类型强制转换将成功。永远不会有例外。好的。

值类型转换是一种特殊类型的转换,它将不同的值类型(例如,从float转换为int)。我稍后再谈。好的。

AS,IS,CAST好的。

在IL中,只支持EDOCX1(cast)和EDOCX1(as)。is运算符被实现为一个带空检查的as,对于这两者的组合来说,它只是一个方便的速记符号。在c_中,您可以将is写成:(myObject as MyFoo) != null。好的。

as只检查对象是否属于特定类型,如果不是,则返回空值。对于静态强制转换案例,我们可以确定这个编译时间,对于动态强制转换案例,我们必须在运行时检查它。好的。

(...)强制转换再次检查类型是否正确,如果不正确则抛出异常。它基本上与as相同,但使用一次投掷而不是null的结果。这可能会让你想知道为什么as不作为异常处理程序实现——好吧,这可能是因为异常相对缓慢。好的。

拳击好的。

当将值类型box转换为对象时,会发生一种特殊类型的转换。基本上,.NET运行时会在堆上复制您的值类型(带有一些类型信息),并将地址作为引用类型返回。换句话说:它将值类型转换为引用类型。好的。

当您有这样的代码时,就会发生这种情况:好的。

1
2
3
int n = 5;
object o = n; // boxes n
int m = (int)o; // unboxes o

取消装箱要求您指定类型。在取消装箱操作过程中,会检查类型(如动态强制转换案例,但由于值类型的继承链很小,因此操作简单得多),如果类型匹配,则会将值复制回堆栈。好的。

您可能期望值类型强制转换对于装箱是隐式的——因为上面没有。唯一允许的拆箱操作是拆箱到确切的值类型。换言之:好的。

1
sbyte m2 = (sbyte)o; // throws an error

值类型转换好的。

如果您将float转换为int,则基本上是转换值。对于基本类型(intptr,(u)int 8/16/32/64,float,double),这些转换在IL中预先定义为conv_*指令,相当于位转换(int8->int16)、截断(int16->int8)和转换(float->int32)。好的。

顺便说一下,这里发生了一些有趣的事情。运行时似乎可以处理堆栈上的多个32位值,因此即使在不希望进行转换的地方,也需要进行转换。例如,考虑:好的。

1
2
sbyte sum = (sbyte)(sbyte1 + sbyte2); // requires a cast. Return type is int32!
int sum = int1 + int2; // no cast required, return type is int32.

延长标志可能很难把你的头包起来。计算机将有符号整数值存储为1个补数。在十六进制符号int8中,这意味着值-1是0xff。那么如果我们把它转换成Int32会发生什么呢?再次说明,-1的1补码值是0xffffffff-所以我们需要将最重要的位传播到"添加"位的其余部分。如果我们正在执行无符号扩展,则需要传播零。好的。

为了说明这一点,下面是一个简单的测试用例:好的。

1
2
3
4
5
byte b1 = 0xFF;
sbyte b2 = (sbyte)b1;
Console.WriteLine((int)b1);
Console.WriteLine((int)b2);
Console.ReadLine();

第一个到int的转换在这里是零扩展,第二个到int的转换是符号扩展。您可能还需要使用"x8"格式字符串来获得十六进制输出。好的。

对于位转换、截断和转换之间的确切区别,我参考解释这些区别的LLVM文档。查找sext/zext/bitcast/fptosi和所有变体。好的。

隐式类型转换好的。

还有一个类别,那就是转换操作符。msdn详细介绍了如何重载转换运算符。基本上,您可以通过重载一个运算符来实现自己的转换。如果希望用户明确指定要强制转换,则添加explicit关键字;如果希望自动执行隐式转换,则添加implicit。基本上你会得到:好的。

1
2
3
4
public static implicit operator byte(Digit d)  // implicit digit to byte conversion operator
{
    return d.value;  // implicit conversion
}

…之后你就可以做像好的。

1
2
Digit d = new Digit(123);
byte b = d;

最佳实践好的。

首先,理解这些差异,这意味着要实现小的测试程序,直到您理解上述所有之间的区别。没有替代品来理解这些东西是如何工作的。好的。

然后,我会坚持这些做法:好的。

  • 速记员在场是有原因的。用最短的符号,可能是最好的。
  • 不要将强制转换用于静态强制转换;只将强制转换用于动态强制转换。
  • 只有在你需要的时候才使用拳击。这方面的细节远远超出了这个答案;基本上我要说的是:使用正确的类型,不要把所有东西都包装起来。
  • 注意编译器关于隐式转换(例如,unsigned/signed)的警告,并始终使用显式转换来解决这些警告。您不想因为符号/零扩展而对奇怪的值感到惊讶。
  • 在我看来,除非你确切知道自己在做什么,否则最好简单地避免隐式/显式转换——简单的方法调用通常更好。这样做的原因是,你可能最终会得到一个松散的例外,而你没有看到它的到来。

好啊。


嗯,这是一个品味和具体的问题,你正在处理。让我们来看两个使用泛型方法的示例。

对于具有"class"约束的泛型方法(使用双重强制转换的最安全方法):

1
2
3
4
5
6
7
8
9
10
public void MyMethod<T>(T myParameter) where T : class
{
   if(myParameter is Employee)
   {
      // we can use 'as' operator because T is class

      Employee e = myParameter as Employee;
      //DO stuff
   }
}

您也可以这样做(这里有一个强制转换操作,但定义了类型的变量可能正确,也可能不正确):

1
2
3
4
5
6
7
8
public void MyMethod<T>(T myParameter) where T : class
{
   Employee e;
   if((e = myParameter as Employee) != null)
   {
      //DO stuff with e
   }
}

对于带有"struct"约束的泛型方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
public void MyMethod<T>(T myParameter) where T : struct
{
   if(myParameter is int)
   {

      // we cant use 'as' operator here because ValueType cannot be null
      // explicit conversion doesn't work either because T could be anything so :

      int e = Convert.ToInt32(myParameter);

      //DO stuff
   }
}

带有显式强制转换的简单方案:

1
2
3
int i = 5;
object o = (object)i;  // boxing
int i2 = (int)o;       // unboxing

我们可以在这里使用显式强制转换,因为我们100%确定要使用什么类型。


如果我需要在投射后使用对象,我将使用as操作符(safe cast)。然后检查是否为空并使用实例。这种方法比is+显式强制转换更有效。

In general, the as operator is more efficient because it actually returns the cast value if the cast can be made successfully. The is operator returns only a Boolean value. It can therefore be used when you just want to determine an object's type but do not have to actually cast it.

(此处提供更多信息)。

我不确定,但我认为is正在引擎盖下使用as,如果强制转换后的对象为空(对于引用类型)/是否引发了异常(对于值类型),则返回。


对于第二个方法,如果强制转换失败,将引发异常。

使用as进行强制转换时,只能使用引用类型。因此,如果要将类型转换为值类型,则必须仍然使用int e = (int) o;方法。

一个很好的经验法则是:如果可以将空值作为值分配给对象,那么可以使用as键入cast。

也就是说,空比较比抛出和捕获异常更快,所以在大多数情况下,使用as应该更快。

不过,我不能确定这是否适用于您的is登记处。在某些多线程条件下,如果另一个线程更改了您要强制转换的对象,它可能会失败。