我正在修改《深入理解C#》第四章关于可空类型的部分,并添加了一个使用 "as" 运算符的小节,该运算符允许您编写以下代码:
object o = ...;
int? x = o as int?;
if (x.HasValue)
{
... // Use x.Value in here
}
我觉得这个方法很棒,可以比C# 1中使用“is”后跟强制转换的方式提高性能-毕竟,这样我们只需要请求一次动态类型检查,然后进行简单的值检查。
不过事实并非如此。下面的示例测试应用程序包含了一个对象数组,其中包含许多空引用、字符串引用和装箱整数,它们的求和功能可以作为基准测试。该基准测试分别测量了在C# 1中使用的代码、使用“as”运算符的代码以及仅供娱乐的LINQ解决方案。令我惊讶的是,在这种情况下,C# 1的代码要快20倍——甚至LINQ代码(考虑到涉及迭代器)也要击败“as”代码。
是.NET对可空类型的isinst
实现太慢了吗?是额外的unbox.any
导致问题吗?还有其他解释吗?目前感觉我必须警告在性能敏感的情况下不要使用这个方法……
结果:
Cast: 10000000 : 121
As: 10000000 : 2211
LINQ: 10000000 : 2143
代码:
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] = "";
values[i+2] = 1;
}
FindSumWithCast(values);
FindSumWithAs(values);
FindSumWithLinq(values);
}
static void FindSumWithCast(object[] values)
{
Stopwatch sw = Stopwatch.StartNew();
int sum = 0;
foreach (object o in values)
{
if (o is int)
{
int x = (int) o;
sum += x;
}
}
sw.Stop();
Console.WriteLine("Cast: {0} : {1}", sum,
(long) sw.ElapsedMilliseconds);
}
static void FindSumWithAs(object[] values)
{
Stopwatch sw = Stopwatch.StartNew();
int sum = 0;
foreach (object o in values)
{
int? x = o as int?;
if (x.HasValue)
{
sum += x.Value;
}
}
sw.Stop();
Console.WriteLine("As: {0} : {1}", sum,
(long) sw.ElapsedMilliseconds);
}
static void FindSumWithLinq(object[] values)
{
Stopwatch sw = Stopwatch.StartNew();
int sum = values.OfType<int>().Sum();
sw.Stop();
Console.WriteLine("LINQ: {0} : {1}", sum,
(long) sw.ElapsedMilliseconds);
}
}
as
。有趣的是,它不能用于其他值类型。实际上,更令人惊讶。 - leppieas
操作符试图将一个对象强制转换为某个类型,如果失败就返回 null。而值类型无法被设置为 null。 - Earlzif (o != null && o.GetType() == typeof(int)) { int x = (int)o; ...
几乎与使用is
一样快,而且比“as 可空”或 LINQ 的方式要快得多。我猜这样我们会两次取消装箱int
。(当类型被密封时,例如结构体,is
检查和GetType() == something
检查之间没有太大区别。) - Jeppe Stig Nielsen