我只是在修改c#深度的第4章,它涉及到可空类型,我增加了一个关于使用“as”操作符的章节,它允许你这样写:

object o = ...;
int? x = o as int?;
if (x.HasValue)
{
    ... // Use x.Value in here
}

我认为这真的很整洁,它可以比c# 1的等效方法提高性能,使用“is”后面加强制转换——毕竟,这样我们只需要要求进行一次动态类型检查,然后进行简单的值检查。

This appears not to be the case, however. I've included a sample test app below, which basically sums all the integers within an object array - but the array contains a lot of null references and string references as well as boxed integers. The benchmark measures the code you'd have to use in C# 1, the code using the "as" operator, and just for kicks a LINQ solution. To my astonishment, the C# 1 code is 20 times faster in this case - and even the LINQ code (which I'd have expected to be slower, given the iterators involved) beats the "as" code.

对于可空类型的isinst的。net实现真的很慢吗?是额外的开箱。是什么导致了这个问题?还有其他解释吗?目前,我觉得我必须在对性能敏感的情况下使用此功能时提出警告……

结果:

演员: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);
    }
}

当前回答

我尝试了准确的类型检查结构

typeof(int) == item. gettype(),它的执行速度与项目为int版本一样快,并且总是返回数字(强调:即使你向数组写入了Nullable<int>,你也需要使用typeof(int))。这里还需要一个额外的null !=项检查。

然而

typeof(int?) == item. gettype()保持较快(相对于item is int?),但总是返回false。

在我看来,typef -construct是精确类型检查的最快方法,因为它使用了RuntimeTypeHandle。由于这种情况下的确切类型与nullable不匹配,我的猜测是,is/as必须在这里做额外的繁重工作,以确保它实际上是nullable类型的实例。

老实说:你的is Nullable<xxx> + HasValue给你买了什么?没什么。您总是可以直接转到基础(值)类型(在本例中)。你要么得到值,要么“不,不是你要的类型的实例”。即使您将(int?)null写入数组,类型检查也将返回false。

其他回答

我没有时间尝试,但你可能想要:

foreach (object o in values)
        {
            int? x = o as int?;

as

int? x;
foreach (object o in values)
        {
            x = o as int?;

您每次都在创建一个新对象,这不能完全解释问题,但可能有助于解决问题。

我尝试了准确的类型检查结构

typeof(int) == item. gettype(),它的执行速度与项目为int版本一样快,并且总是返回数字(强调:即使你向数组写入了Nullable<int>,你也需要使用typeof(int))。这里还需要一个额外的null !=项检查。

然而

typeof(int?) == item. gettype()保持较快(相对于item is int?),但总是返回false。

在我看来,typef -construct是精确类型检查的最快方法,因为它使用了RuntimeTypeHandle。由于这种情况下的确切类型与nullable不匹配,我的猜测是,is/as必须在这里做额外的繁重工作,以确保它实际上是nullable类型的实例。

老实说:你的is Nullable<xxx> + HasValue给你买了什么?没什么。您总是可以直接转到基础(值)类型(在本例中)。你要么得到值,要么“不,不是你要的类型的实例”。即使您将(int?)null写入数组,类型检查也将返回false。

为了使这个答案保持最新,值得一提的是,对于c# 7.1和。net 4.7来说,本页上的大部分讨论现在都是毫无意义的,因为c# 7.1和。net 4.7支持简洁的语法,而且还能生成最好的IL代码。

OP最初的例子是…

object o = ...;
int? x = o as int?;
if (x.HasValue)
{
    // ...use x.Value in here
}

变得简单…

if (o is int x)
{
    // ...use x in here
}

我发现新语法的一个常见用途是当你编写一个。net值类型(即c#中的struct)实现IEquatable<MyStruct>(大多数应该)。在实现强类型Equals(MyStruct other)方法之后,你现在可以优雅地重定向非类型Equals(Object obj)覆盖(继承自Object)到它,如下所示:

public override bool Equals(Object obj) => obj is MyStruct o && Equals(o);

 


附录:在这个答案中给出的前两个示例函数的发布构建IL代码(分别)在这里。虽然新语法的IL代码确实少了1个字节,但它主要是通过不调用(而不是两个)并尽可能避免开箱操作来获得最大的胜利。

// static void test1(Object o, ref int y)
// {
//     int? x = o as int?;
//     if (x.HasValue)
//         y = x.Value;
// }

[0] valuetype [mscorlib]Nullable`1<int32> x
        ldarg.0
        isinst [mscorlib]Nullable`1<int32>
        unbox.any [mscorlib]Nullable`1<int32>
        stloc.0
        ldloca.s x
        call instance bool [mscorlib]Nullable`1<int32>::get_HasValue()
        brfalse.s L_001e
        ldarg.1
        ldloca.s x
        call instance !0 [mscorlib]Nullable`1<int32>::get_Value()
        stind.i4
L_001e: ret

// static void test2(Object o, ref int y)
// {
//     if (o is int x)
//         y = x;
// }

[0] int32 x,
[1] object obj2
        ldarg.0
        stloc.1
        ldloc.1
        isinst int32
        ldnull
        cgt.un
        dup
        brtrue.s L_0011
        ldc.i4.0
        br.s L_0017
L_0011: ldloc.1
        unbox.any int32
L_0017: stloc.0
        brfalse.s L_001d
        ldarg.1
        ldloc.0
        stind.i4
L_001d: ret

进一步的测试证实了我关于新c# 7语法性能优于以前可用选项的评论,请参阅这里(特别是示例'D')。

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);
        FindSumWithAsAndHas(values);
        FindSumWithAsAndIs(values);


        FindSumWithIsThenAs(values);
        FindSumWithIsThenConvert(values);

        FindSumWithLinq(values);



        Console.ReadLine();
    }

    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 FindSumWithAsAndHas(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 and Has: {0} : {1}", sum,
                          (long)sw.ElapsedMilliseconds);
    }


    static void FindSumWithAsAndIs(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            int? x = o as int?;
            if (o is int)
            {
                sum += x.Value;
            }
        }
        sw.Stop();
        Console.WriteLine("As and Is: {0} : {1}", sum,
                          (long)sw.ElapsedMilliseconds);
    }







    static void FindSumWithIsThenAs(object[] values)
    {
        // Apple-to-apple comparison with Cast routine above.
        // Using the similar steps in Cast routine above,
        // the AS here cannot be slower than Linq.



        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {

            if (o is int)
            {
                int? x = o as int?;
                sum += x.Value;
            }
        }
        sw.Stop();
        Console.WriteLine("Is then As: {0} : {1}", sum,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindSumWithIsThenConvert(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {            
            if (o is int)
            {
                int x = Convert.ToInt32(o);
                sum += x;
            }
        }
        sw.Stop();
        Console.WriteLine("Is then Convert: {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);
    }
}

输出:

Cast: 10000000 : 456
As and Has: 10000000 : 2103
As and Is: 10000000 : 2029
Is then As: 10000000 : 1376
Is then Convert: 10000000 : 566
LINQ: 10000000 : 1811

(编辑:2010-06-19)

注意:之前的测试是在VS内部完成的,配置调试,使用VS2009,使用Core i7(公司开发机)。

以下是在我的机器上使用Core 2 Duo,使用VS2010完成的

Inside VS, Configuration: Debug

Cast: 10000000 : 309
As and Has: 10000000 : 3322
As and Is: 10000000 : 3249
Is then As: 10000000 : 1926
Is then Convert: 10000000 : 410
LINQ: 10000000 : 2018




Outside VS, Configuration: Debug

Cast: 10000000 : 303
As and Has: 10000000 : 3314
As and Is: 10000000 : 3230
Is then As: 10000000 : 1942
Is then Convert: 10000000 : 418
LINQ: 10000000 : 1944




Inside VS, Configuration: Release

Cast: 10000000 : 305
As and Has: 10000000 : 3327
As and Is: 10000000 : 3265
Is then As: 10000000 : 1942
Is then Convert: 10000000 : 414
LINQ: 10000000 : 1932




Outside VS, Configuration: Release

Cast: 10000000 : 301
As and Has: 10000000 : 3274
As and Is: 10000000 : 3240
Is then As: 10000000 : 1904
Is then Convert: 10000000 : 414
LINQ: 10000000 : 1936

在我看来,isinst在可空类型上真的很慢。在FindSumWithCast方法中我改变了

if (o is int)

to

if (o is int?)

这也大大降低了执行速度。我能看到的IL的唯一不同是

isinst     [mscorlib]System.Int32

变为

isinst     valuetype [mscorlib]System.Nullable`1<int32>