在编程接口时,我发现我做了很多强制转换或对象类型转换。
这两种转换方法有什么区别吗?如果是的话,是否有成本差异,或者这对我的程序有什么影响?
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);
}
}
另外,“一般来说”首选的方法是什么?
请忽略Jon Skeet的建议,re:避免测试-强制转换模式,即:
if (randomObject is TargetType)
{
TargetType foo = randomObject as TargetType;
// Do something with foo
}
认为这比强制转换和空测试花费更多的想法是错误的:
TargetType convertedRandomObject = randomObject as TargetType;
if (convertedRandomObject != null)
{
// Do stuff with convertedRandomObject
}
这是一种不起作用的微观优化。我运行了一些实际的测试,测试-强制转换实际上比强制转换-空比较更快,而且更安全,因为如果强制转换失败,则不可能在if之外的作用域中有空引用。
如果您想知道为什么测试-强制转换更快,或者至少不会更慢,有一个简单而复杂的原因。
简单:即使是简单的编译器也会将两个类似的操作(如test-and-cast)合并为一个测试和分支。强制转换-空测试可能强制执行两个测试和一个分支,一个用于类型测试和失败时转换为空,一个用于空检查本身。至少,它们都将优化为单个测试和分支,因此测试-强制转换既不会比强制转换-空测试慢也不会快。
复杂:为什么测试-强制转换更快:强制转换-空测试将另一个变量引入到外部作用域,编译器必须实时跟踪这个变量,并且它可能无法优化掉这个变量,这取决于你的控制流有多复杂。相反,“测试-强制转换”只在分隔的作用域内引入新变量,这样编译器就知道该变量在作用域退出后失效,从而更好地优化寄存器分配。
所以,请让这个“强制转换-空测试比测试-强制转换更好”的建议死掉吧。请。测试-强制转换既安全又快速。
我的答案只是在我们不检查类型和转换后不检查null的情况下的速度。我在Jon Skeet的代码中添加了两个额外的测试:
using System;
using System.Diagnostics;
class Test
{
const int Size = 30000000;
static void Main()
{
object[] values = new object[Size];
for (int i = 0; i < Size; i++)
{
values[i] = "x";
}
FindLengthWithIsAndCast(values);
FindLengthWithIsAndAs(values);
FindLengthWithAsAndNullCheck(values);
FindLengthWithCast(values);
FindLengthWithAs(values);
Console.ReadLine();
}
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);
}
static void FindLengthWithCast(object[] values)
{
Stopwatch sw = Stopwatch.StartNew();
int len = 0;
foreach (object o in values)
{
string a = (string)o;
len += a.Length;
}
sw.Stop();
Console.WriteLine("Cast: {0} : {1}", len,
(long)sw.ElapsedMilliseconds);
}
static void FindLengthWithAs(object[] values)
{
Stopwatch sw = Stopwatch.StartNew();
int len = 0;
foreach (object o in values)
{
string a = o as string;
len += a.Length;
}
sw.Stop();
Console.WriteLine("As: {0} : {1}", len,
(long)sw.ElapsedMilliseconds);
}
}
结果:
Is and Cast: 30000000 : 88
Is and As: 30000000 : 93
As and null check: 30000000 : 56
Cast: 30000000 : 66
As: 30000000 : 46
不要像我一样专注于速度,因为所有这些都是非常非常快的。