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

时间:2015-09-25 06:52:15

标签: c# .net

哪种方法是输入和检查的最佳做法?

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

OR

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

5 个答案:

答案 0 :(得分:14)

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

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

答案 1 :(得分:4)

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

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

动态广播和静态广播

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

基本上,如果您执行(Foo)myObject,那么您实际上正在进行动态广告,如果您正在执行(object)myFoo(或只是{{1}你正在做静态演员

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

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

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

As,is,cast

在IL中,唯一支持的是InvalidCastException(强制转换)和castclass(as)。 isinst运算符实现为带有空检查的is,并且只是方便的简写符号,两者的组合。在C#中,您可以将as写为:is

(myObject as MyFoo) != null只检查某个对象是否属于特定类型,如果不是,则返回null。对于 static cast 情况,我们可以确定这个编译时,对于动态强制转换情况,我们必须在运行时检查它。

再次

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

<强>拳击

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

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

box

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

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

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

值类型广播

如果您要将sbyte m2 = (sbyte)o; // throws an error 投射到float,则基本上转换该值。对于基本类型(IntPtr,(u)int 8/16/32/64,float,double),这些转换在IL中作为int指令预先定义,这相当于位转换(int8 - &gt; ; int16),截断(int16 - &gt; int8)和转换(float - &gt; int32)。

这里有一些有趣的事情。运行时似乎在堆栈上的大量32位值上工作,因此即使在您不期望它们的地方也需要转换。例如,考虑:

conv_*

签名扩展可能很难解决问题。计算机将有符号整数值存储为1补码。在十六进制表示法中,int8,这意味着值-1是0xFF。那么如果我们将它转​​换为int32会发生什么?同样,-1的1补码值是0xFFFFFFFF - 所以我们需要将最高有效位传播到其余的&#39;添加&#39;位。如果我们正在进行无符号扩展,我们需要传播零。

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

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

对int的第一次转换是零扩展,第二次转换为int是符号扩展。您也可能希望使用&#34; x8&#34;格式化字符串以获取十六进制输出。

对于位转换,截断和转换之间的确切差异,我参考解释差异的LLVM documentation。查找byte b1 = 0xFF; sbyte b2 = (sbyte)b1; Console.WriteLine((int)b1); Console.WriteLine((int)b2); Console.ReadLine(); / sext / zext / bitcast以及所有变体。

隐式类型转换

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

implicit

...之后你可以做像

这样的事情
public static implicit operator byte(Digit d)  // implicit digit to byte conversion operator
{
    return d.value;  // implicit conversion
}

最佳做法

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

然后,我坚持这些做法:

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

答案 2 :(得分:1)

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

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

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

表示,null比较比抛出和捕获异常更快,因此在大多数情况下,使用as应该更快。

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

答案 3 :(得分:1)

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

更有效
  

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

(更多信息here)。

不确定有关它但我认为is正在使用as并且如果转换后的对象为null(如果< em>引用类型)/抛出异常(如果是值类型)或不是。

答案 4 :(得分:1)

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

对于&#39; class&#39;的通用方法约束(双重投射最安全的方法):

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

对于使用&#39; struct&#39;的通用方法约束:

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%确定我们使用的是什么类型。