哪种方法是输入和检查的最佳做法?
Employee e = o as Employee;
if(e != null)
{
//DO stuff
}
OR
if(o is Employee)
{
Employee e = (Employee) o;
//DO stuff
}
答案 0 :(得分:14)
至少有两种可能性,一种用于类型检查,另一种用于模式匹配。每个都有自己的目的,这取决于具体情况:
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();
as
运营商也无法在struct
上使用。这只是因为如果演员表失败并且null
永远不会是struct
,运营商想要返回null
。
var isMyType = source is MyType;
这很少被正确使用。此类型检查仅在您只需知道某些特定类型,但您不必使用该对象时才有用。
if(source is MyType)
DoSomething();
else
DoSomethingElse();
if (source is MyType myType)
DoSomething(myType);
模式匹配是dotnet框架中与强制转换相关的最新功能。但您也可以使用switch statement和when 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的代理人。
然后,我坚持这些做法:
答案 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%确定我们使用的是什么类型。