投射vs在CLR中使用'as'关键字
在编程接口时,我发现我正在进行大量的转换或对象类型转换。
这两种转换方法有区别吗? 如果是这样,是否有成本差异,或者这对我的计划有什么影响?
public interface IMyInterface
{
void AMethod();
}
public class MyClass : IMyInterface
{
public void AMethod()
{
//Do work
}
// Other helper methods....
}
public class Implementation
{
IMyInterface _MyObj;
MyClass _myCls1;
MyClass _myCls2;
public Implementation()
{
_MyObj = new MyClass();
// What is the difference here:
_myCls1 = (MyClass)_MyObj;
_myCls2 = (_MyObj as MyClass);
}
}
另外,什么是“一般”的首选方法?
线下的答案是在2008年写的。
C#7引入了模式匹配,它已经在很大程度上取代了as
运算符,正如您现在可以编写的那样:
if (randomObject is TargetType tt)
{
// Use tt here
}
请注意, tt
在此之后仍然在范围内,但未明确分配。 (这绝对是内进行分配, if
身体。)这是在某些情况下有点烦,所以如果你真的关心引入在每一个范围的变量可能的最小数目,你仍然可能需要使用is
后跟铸造。
到目前为止,我认为没有任何答案(在开始这个答案的时候!)已经真正解释了它在哪里值得使用。
不要这样做:
// Bad code - checks type twice for no reason
if (randomObject is TargetType)
{
TargetType foo = (TargetType) randomObject;
// Do something with foo
}
这不仅是检查两次,而且它可能检查不同的事情,如果randomObject
是一个字段而不是局部变量。 如果另一个线程更改了这randomObject
之间的randomObject
值,那么“if”可能会通过,但是会导致转换失败。
如果randomObject
真的应该是TargetType
一个实例,即如果它不是,那就意味着有一个bug,那么投射就是正确的解决方案。 这会立即抛出异常,这意味着在不正确的假设下不会做更多的工作,并且异常正确地显示了错误的类型。
// This will throw an exception if randomObject is non-null and
// refers to an object of an incompatible type. The cast is
// the best code if that's the behaviour you want.
TargetType convertedRandomObject = (TargetType) randomObject;
如果randomObject
可能是TargetType
一个实例并且TargetType
是引用类型,那么使用如下代码:
TargetType convertedRandomObject = randomObject as TargetType;
if (convertedRandomObject != null)
{
// Do stuff with convertedRandomObject
}
如果randomObject
可能是TargetType
一个实例,并且TargetType
是一个值类型,那么我们不能as
使用TargetType
本身as
使用,但我们可以使用一个可为空的类型:
TargetType? convertedRandomObject = randomObject as TargetType?;
if (convertedRandomObject != null)
{
// Do stuff with convertedRandomObject.Value
}
(注意:目前这个速度实际上比“+ cast”更慢,我认为它更加优雅和一致,但是我们走了。)
如果你真的不需要转换的值,但你只需要知道它是否是TargetType的一个实例,那么is
运算符就是你的朋友。 在这种情况下,TargetType是引用类型还是值类型都无关紧要。
有可能是涉及仿制药,而其他情况下, is
非常有用的(因为你可能不知道T是否是引用类型或没有,所以你不能因为使用),但他们是比较模糊的。
我几乎可以肯定,使用is
该值类型的情况下现在之前,没有使用可空类型和思想as
共同:)
编辑:请注意,以上没有谈到性能,除了值类型的情况下,我已经注意到拆箱到可空值类型实际上是较慢 - 但一致。
根据naasking的回答,is-and-cast或is-and-as与现代JIT一样快,可以检查,如下面的代码所示:
using System;
using System.Diagnostics;
using System.Linq;
class Test
{
const int Size = 30000000;
static void Main()
{
object[] values = new object[Size];
for (int i = 0; i < Size - 2; i += 3)
{
values[i] = null;
values[i + 1] = "x";
values[i + 2] = new object();
}
FindLengthWithIsAndCast(values);
FindLengthWithIsAndAs(values);
FindLengthWithAsAndNullCheck(values);
}
static void FindLengthWithIsAndCast(object[] values)
{
Stopwatch sw = Stopwatch.StartNew();
int len = 0;
foreach (object o in values)
{
if (o is string)
{
string a = (string) o;
len += a.Length;
}
}
sw.Stop();
Console.WriteLine("Is and Cast: {0} : {1}", len,
(long)sw.ElapsedMilliseconds);
}
static void FindLengthWithIsAndAs(object[] values)
{
Stopwatch sw = Stopwatch.StartNew();
int len = 0;
foreach (object o in values)
{
if (o is string)
{
string a = o as string;
len += a.Length;
}
}
sw.Stop();
Console.WriteLine("Is and As: {0} : {1}", len,
(long)sw.ElapsedMilliseconds);
}
static void FindLengthWithAsAndNullCheck(object[] values)
{
Stopwatch sw = Stopwatch.StartNew();
int len = 0;
foreach (object o in values)
{
string a = o as string;
if (a != null)
{
len += a.Length;
}
}
sw.Stop();
Console.WriteLine("As and null check: {0} : {1}", len,
(long)sw.ElapsedMilliseconds);
}
}
在我的笔记本电脑上,这些全部在大约60ms内执行。 需要注意两点:
所以,我们不要担心表现。 让我们担心正确性和一致性。
我认为is-and-cast(或is-and-as)在处理变量时都是不安全的,因为它引用的值的类型可能因测试和演员之间的另一个线程而改变。 这将是一个非常罕见的情况 - 但我宁愿有一个我可以持续使用的约定。
我还认为,as-then-null-check可以更好地分离关注点。 我们有一条声明试图进行转换,然后有一条声明使用结果。 is-and-cast或is-and-as执行测试,然后再尝试转换该值。
换句话说,任何人都会写:
int value;
if (int.TryParse(text, out value))
{
value = int.Parse(text);
// Use value
}
这就是什么 - 演员在做什么 - 虽然显然是以一种相对便宜的方式。
“as”将返回NULL,如果无法投射。
之前的投射会引发异常。
对于表演来说,举办例外通常会花费更多的时间。
这是另一个答案,与一些IL比较。 考虑这个班级:
public class MyClass
{
public static void Main()
{
// Call the 2 methods
}
public void DirectCast(Object obj)
{
if ( obj is MyClass)
{
MyClass myclass = (MyClass) obj;
Console.WriteLine(obj);
}
}
public void UsesAs(object obj)
{
MyClass myclass = obj as MyClass;
if (myclass != null)
{
Console.WriteLine(obj);
}
}
}
现在看看每种方法产生的IL。 即使操作码对您来说没有任何意义,您可以看到一个主要区别 - 在DirectCast方法中调用isinst后跟castclass。 所以基本上两个电话而不是一个。
.method public hidebysig instance void DirectCast(object obj) cil managed
{
// Code size 22 (0x16)
.maxstack 8
IL_0000: ldarg.1
IL_0001: isinst MyClass
IL_0006: brfalse.s IL_0015
IL_0008: ldarg.1
IL_0009: castclass MyClass
IL_000e: pop
IL_000f: ldarg.1
IL_0010: call void [mscorlib]System.Console::WriteLine(object)
IL_0015: ret
} // end of method MyClass::DirectCast
.method public hidebysig instance void UsesAs(object obj) cil managed
{
// Code size 17 (0x11)
.maxstack 1
.locals init (class MyClass V_0)
IL_0000: ldarg.1
IL_0001: isinst MyClass
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: brfalse.s IL_0010
IL_000a: ldarg.1
IL_000b: call void [mscorlib]System.Console::WriteLine(object)
IL_0010: ret
} // end of method MyClass::UsesAs
isinst关键字与castclass
这篇博客文章比较了两种方式。 他的总结是:
我个人总是使用As,因为它易于阅读,并由.NET开发团队(或Jeffrey Richter)推荐。
链接地址: http://www.djcxy.com/p/12809.html上一篇: Casting vs using the 'as' keyword in the CLR
下一篇: What's the difference between the 'ref' and 'out' keywords?