C#中用于铸造的最佳做法是什么?

键入铸造和检查的最佳做法是哪种方法?

Employee e = o as Employee; if(e != null) { //DO stuff } 

要么

 if(o is Employee) { Employee e = (Employee) o; //DO stuff } 

至少有两种铸造可能性,一种用于型式检查。 每个都有自己的目的,这取决于具体情况:

硬铸

 var myObject = (MyType)source; 

如果您完全确定给定对象是否属于该类型,则通常会这样做。 如果您订阅了事件处理程序并将发送方对象强制转换为正确的类型以便对其进行处理,则可以使用它。

 private void OnButtonClick(object sender, EventArgs e) { var button = (Button)sender; button.Text = "Disabled"; button.Enabled = false; } 

软铸造

 var myObject = source as MyType; if (myObject != null) // Do Something 

如果你不知道你是否真的有这种类型,通常会使用它。 因此,只需尝试转换它,如果不可能,只需返回null。 一个常见的例子是,如果只有在某个界面满载时才需要做某事:

 var disposable = source as IDisposable; if(disposable != null) disposable.Dispose(); 

键入检查

 var isMyType = source is MyType; 

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

 if(source is MyType) DoSomething(); else DoSomethingElse(); 

我认为这是一个很好的问题,值得认真和详细的回答。 类型转换是C#实际上有很多不同的东西。

与C#不同,像C ++这样的语言对这些语言非常严格,所以我将在那里使用命名作为参考。 我一直认为最好了解事情是如何运作的,所以我会在这里为你详细介绍。 开始:

动态模型和静态模型

C#具有值类型和引用类型。 引用类型始终遵循从Object开始的inheritance链。

基本上如果你做(Foo)myObject ,你实际上是在进行动态转换 ,如果你正在做(object)myFoo (或者只是object o = myFoo )你正在进行静态转换

动态转换需要您进行类型检查,也就是说,运行时将检查您要转换的对象是否属于该类型。 毕竟,你正在抛弃inheritance树,所以你不妨完全抛弃其他东西。 如果是这种情况,您将最终得到InvalidCastException 。 因此,动态强制转换需要运行时类型信息(例如,它需要运行时知道哪个对象具有哪种类型)。

静态强制转换不需要进行类型检查。 在这种情况下,我们在inheritance树中进行了扩展,因此我们已经知道类型转换将成功。 永远不会抛出任何例外。

值类型转换是一种特殊类型的转换,它转换不同的值类型(f.ex.从float到int)。 我稍后会进入。

as,是,演员

在IL中,唯一支持的东西是castclass (cast)和isinst (as)。 is运算符实现为null检查,并且只是两者组合的便捷简写符号 。 在C#中,你可以写为:( (myObject as MyFoo) != null

简单地检查对象是否属于特定类型,如果不是则返回null。 对于静态转换的情况,我们可以确定这个编译时,对于动态转换情况,我们必须在运行时检查它。

(...)再次强制转换检查类型是否正确,如果不是则抛出exception。 它与as基本相同,但是使用throw而不是null结果。 这可能会让你想知道为什么没有实现为exception处理程序 – 好吧,这可能是因为exception相对较慢。

拳击

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

当你有这样的代码时会发生这种情况:

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

取消装箱需要您指定类型。 在取消装箱操作期间,检查类型(类似于动态转换的情况,但它更简单,因为值类型的inheritance链是微不足道的),如果类型匹配,则将值复制回堆栈。

您可能期望值类型转换对于拳击是隐含的 – 好吧,因为上面它们不是。 唯一允许的拆箱操作是拆箱到准确的值类型。 换一种说法:

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

值类型转换

如果你将一个float为一个int ,你基本上就是转换它的值。 对于基本类型(IntPtr,(u)int 8/ conv_* ,float,double),这些转换在IL中作为conv_*指令预先定义,它们相当于位转换(int8 – > int16),截断(int16 – > int8)和转换(float – > int32)。

这里有一些有趣的事情。 运行时似乎可以处理堆栈中的大量32位值,因此即使在您不希望它们的位置也需要转换。 例如,考虑:

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

标志扩展可能很难处理。 计算机将有符号整数值存储为1补码。 在hex表示法中,int8,这意味着值-1是0xFF。 那么如果我们将它转​​换为int32会发生什么? 同样,-1的补码值为0xFFFFFFFF – 因此我们需要将最高有效位传播到其余的“添加”位。 如果我们正在进行无符号扩展,我们需要传播零。

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

 byte b1 = 0xFF; sbyte b2 = (sbyte)b1; Console.WriteLine((int)b1); Console.WriteLine((int)b2); Console.ReadLine(); 

对int的第一次转换是零扩展,第二次转换为int是符号扩展。 您还可能希望使用“x8”格式字符串来获取hex输出。

有关位转换,截断和转换之间的确切差异,请参阅解释其差异的LLVM文档 。 寻找sext / zext / bitcast / fptosi和所有变种。

隐式类型转换

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

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

…之后你可以做类似的事情

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

最佳做法

首先,了解差异,这意味着实施小型测试程序,直到您理解上述所有之间的区别。 没有理解How Stuff Works的代理人。

然后,我坚持这些做法:

  • 短缺是有原因的。 使用最短的符号,它可能是最好的符号。
  • 不要将铸件用于静电铸件; 仅使用演员表进行动态演员表。
  • 如果需要,只能使用拳击。 这个细节远远超出了这个答案; 基本上我所说的是:使用正确的类型,不要包装一切。
  • 请注意有关隐式转换的编译器警告(f.ex. unsigned / signed),并始终使用显式强制转换解决它们。 由于符号/零扩展,您不希望因奇怪的值而感到意外。
  • 在我看来,除非你确切地知道你在做什么,否则最好简单地避免隐式/显式转换 – 一个简单的方法调用通常会更好。 这样做的原因是你可能最终得到一个松散的例外,你没看到即将到来。

使用第二种方法,如果转换失败,则抛出exception。

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

一个好的经验法则是:如果您可以将null指定为对象的值,则可以使用as键入cast。

也就是说,null比较比抛出和捕获exception更快,因此在大多数情况下,使用as应该更快。

我不能诚实地说,如果这适用于你is签到虽然。 它可能会在某些multithreading条件下失败,其中另一个线程会更改您正在投射的对象。

如果我需要在转换后使用该对象,我会使用as (safe-cast)运算符。 然后我检查null并使用实例。 此方法比+显式转换更有效

通常,as运算符更有效,因为如果可以成功进行强制转换,它实际上会返回强制转换值。 is运算符仅返回一个布尔值。 因此,当您只想确定对象的类型但不必实际投射它时,可以使用它。

(更多信息请点击此处 )。

不确定它,但我认为这is在引擎盖下使用as如果转换后的对象为null(在引用类型的情况下)/抛出exception(如果是值类型 ),则返回。

嗯,这是你正在处理的问题的品味和细节问题。 让我们看一下使用generics方法的两个例子。

对于具有“类”约束的通用方法(使用双重转换的最安全方法):

 public void MyMethod(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 } } 

你也可以做这样的事情(这里有一个演员操作,但定义的类型变量可能是也可能不正确):

 public void MyMethod(T myParameter) where T : class { Employee e; if((e = myParameter as Employee) != null) { //DO stuff with e } } 

对于具有’struct’约束的generics方法:

 public void MyMethod(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 } } 

显式强制转换的简单场景:

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

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