【问题标题】:Which is the best practice in C# for type casting? [duplicate]C# 中类型转换的最佳实践是什么? [复制]
【发布时间】:2015-12-22 22:43:51
【问题描述】:

哪种方法是类型转换和检查的最佳实践?

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

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

【问题讨论】:

  • 您的问题主要基于意见。我投票决定关闭副本,因为我相信它对您更有用,看看有什么不同是为了做出自己的决定
  • 我一直都是as,反正它需要被铸造。
  • 我的问题是是否有任何性能差异?不是 is 和 as 关键字的区别。
  • 您的问题与性能完全无关。你只是问“哪个最好”。
  • 这对我来说几乎是一个很好的问题,它提出了一个非常好的和具有挑战性的观点,但似乎是基于意见的答案。通常没有最好的方式来做事。不过,通常有一种最快或最有效的方法。

标签: c# .net


【解决方案1】:

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

Hard cast

var myObject = (MyType)source;

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

private void OnButtonClick(object sender, EventArgs e)
{
    var button = (Button)sender;

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

Soft cast

var myObject = source as MyType;

if (myObject != null)
    // Do Something

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

var disposable = source as IDisposable;

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

as 运算符也不能用于struct。这仅仅是因为操作员想要返回 null 以防转换失败并且 struct 永远不能是 null

Type check

var isMyType = source is MyType;

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

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

Pattern matching

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

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

switch (source)
{
    case SpecialType s when s.SpecialValue > 5
        DoSomething(s);
    case AnotherType a when a.Foo == "Hello"
        SomethingElse(a);
}

【讨论】:

  • 编译器是否在软转换(as)之前进行“is”检查?
  • 感谢您的链接。这很有帮助@Oliver
  • 也许您还应该提到您不能将as 用于结构类型。
  • @OlivierJacot-Descombes:是的,你是对的。刚刚更新了我的答案。
  • @vrnithinkumar:这可能有助于回答您的问题:ericlippert.com/2010/09/16/is-is-as-or-is-as-is
【解决方案2】:

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

与 C# 不同,像 C++ 这样的语言对这些非常严格,所以我将使用那里的命名作为参考。我一直认为最好了解事物的工作原理,因此我将在这里为您详细介绍。如下:

动态转换和静态转换

C# 有值类型和引用类型。引用类型始终遵循继承链,从 Object 开始。

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

动态转换 需要您进行类型检查,也就是说,运行时将检查您要转换的对象是否属于该类型。毕竟,您正在向下转换继承树,因此您不妨完全转换为其他东西。如果是这种情况,您最终会得到一个InvalidCastException。因此,动态转换需要运行时类型信息(例如,它要求运行时知道什么对象具有什么类型)。

静态转换不需要类型检查。在这种情况下,我们在继承树中进行转换,所以我们已经知道类型转换会成功。永远不会抛出异常。

值类型转换 是一种特殊类型的转换,可将不同的值类型(例如从浮点数转换为整数)。我稍后再谈。

照原样

在 IL 中,唯一受支持的是 castclass (cast) 和 isinst (as)。 is 操作符被实现为带有空值检查的as,只不过是一种方便的速记符号,可以将它们结合起来。在 C# 中,您可以将 is 写为:(myObject as MyFoo) != null

as 只是检查对象是否属于特定类型,如果不是则返回 null。对于 static cast 的情况,我们可以确定这个编译时间,对于 dynamic cast 的情况,我们必须在运行时检查这个。

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

拳击

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

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

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

拆箱需要您指定类型。在拆箱操作期间,检查类型(类似于 dynamic cast 的情况,但它更简单,因为值类型的继承链是微不足道的),如果类型匹配,则将值复制回堆栈。

您可能期望值类型转换对于装箱来说是隐式的——好吧,因为上面它们不是。唯一允许的拆箱操作是拆箱到确切的值类型。换句话说:

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 位值,因此即使在您不期望它们的地方也需要转换。例如,考虑:

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 - 所以我们需要将最高有效位传播到其余“添加”位。如果我们在做无符号扩展,我们需要传播零。

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

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

第一次转换为 int 是零扩展,第二次转换为 int 是符号扩展。您可能还想使用“x8”格式字符串来获取十六进制输出。

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

隐式类型转换

还有一个类别,那就是转换运算符。 MSDN 详细介绍了如何重载conversion operators。基本上你可以做的是通过重载一个操作符来实现你自己的转换。如果您希望用户明确指定您打算投射,请添加 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;

最佳做法

首先,了解差异,这意味着实施小型测试程序,直到您了解上述所有内容之间的区别。没有替代品可以理解 Stuff 的工作原理。

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

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

【讨论】:

    【解决方案3】:

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

    使用as 进行强制转换时,只能使用引用类型。所以如果你要转换成值类型,你仍然必须使用int e = (int) o; 方法。

    一个好的经验法则是:如果可以将 null 作为值分配给对象,则可以使用 as 进行类型转换。

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

    我不能老实说,这是否适用于您的is 支票。在某些多线程条件下,它可能会失败,其中另一个线程更改了您正在投射的对象。

    【讨论】:

    • @M.kazemAkhgary 但仍然没有将 o 转换为 Employee ,对吧?
    • 吧多线程的东西,第二种方法的cast怎么会失败?
    • @Rawling 多线程可能是让它失败的唯一方法,我认为。
    【解决方案4】:

    如果我需要在转换后使用对象,我会使用as(安全转换)运算符。然后我检查 null 并使用该实例。这种方法比is+显式强制转换效率更高

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

    (更多信息here)。

    不确定,但我认为is 在后台使用as,如果转换后的对象为空,则返回(如果是引用类型) / 是否抛出异常(在值类型的情况下)。

    【讨论】:

      【解决方案5】:

      嗯,这是您正在处理的问题的品味和细节问题。让我们看两个使用泛型方法的示例。

      对于具有“类”约束的泛型方法(双重强制转换最安全的方法):

      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
         }
      }
      

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

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

      对于具有“结构”约束的通用方法:

      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
         }
      }
      

      显式转换的简单场景:

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

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

      【讨论】:

      • 您的第一个代码示例确实尝试强制转换两次,调用 as first 并检查 null 。 msdn.microsoft.com/en-us/library/ms182271.aspx
      • @Console 谢谢,这也是一个选项,我刚刚将它添加到我更新的答案中,但这并不总是一个好的选项,因为它需要定义之前可能不是正确类型的变量做这个演员。如果我们有 100 种类型呢?
      猜你喜欢
      • 1970-01-01
      • 2016-09-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-21
      • 1970-01-01
      • 1970-01-01
      • 2011-06-07
      相关资源
      最近更新 更多