Surpresa de desempenho com tipos "as" e anuláveis

Estou apenas revisando o capítulo 4 do C # em Profundidade, que lida com tipos anuláveis, e estou adicionando uma seção sobre como usar o operador "as", que permite escrever:

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

Eu pensei que isso era realmente legal, e que poderia melhorar o desempenho sobre o equivalente em C # 1, usando "é" seguido de um elenco - afinal, só precisamos pedir verificação dinâmica de tipo uma vez e, em seguida, uma simples verificação de valor .

Este não parece ser o caso, no entanto. Eu incluí um aplicativo de teste de amostra abaixo, que basicamente soma todos os inteiros dentro de uma matriz de objetos - mas a matriz contém muitas referências nulas e referências de string, bem como inteiros em caixa. O benchmark mede o código que você teria que usar em C # 1, o código usando o operador "as" e apenas para chutar uma solução LINQ. Para minha surpresa, o código C # 1 é 20 vezes mais rápido neste caso - e até mesmo o código LINQ (que eu esperava ser mais lento, considerando os iteradores envolvidos) bate o código "as".

A implementação do .NET éisinst para tipos anuláveis ​​apenas muito lentos? É o adicionalunbox.any que causa o problema? Existe outra explicação para isso? No momento, parece que vou ter que incluir um alerta contra o uso em situações sensíveis ao desempenho ...

Resultados:

Elenco: 10000000: 121
Como: 10000000: 2211
LINQ: 10000000: 2143

Código:

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);
    }
}

questionAnswers(10)

yourAnswerToTheQuestion