2009-10-17 4 views
292

Я просто пересматриваю главу 4 из C# в Depth, которая имеет дело с типами NULL, и я добавляю раздел об использовании оператора «as», который позволяет вам писать:Удивительный сюрприз с типами «как» и «с нулевым числом»

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

Я думал, что это было очень аккуратно, и что это может улучшить производительность по сравнению с C# 1 эквивалент, используя «в», а затем броском - в конце концов, таким образом, нам нужно только попросить для проверки один раз динамический тип , а затем простая проверка значения.

Это, похоже, не так. Я включил тестовое приложение ниже, которое в основном суммирует все целые числа в массиве объектов, но массив содержит множество нулевых ссылок и ссылок на строки, а также целые числа в штучной упаковке. Эталонный показатель измеряет код, который вы должны использовать в C# 1, код с использованием оператора «as» и просто для решения LINQ. К моему удивлению, код C# 1 в этом случае в 20 раз быстрее - и даже код LINQ (который я ожидал бы медленнее, учитывая задействованные итераторы) превосходит код «как».

Является ли реализация .NET для isinst для типов с нулевым значением только очень медленным? Является ли это дополнительным unbox.any, что вызывает проблему? Есть ли еще одно объяснение этому? В настоящее время он чувствует, как я буду иметь, чтобы включить предупреждение против использования этого в производительности сложных ситуациях ...

Результаты:

В ролях: 10000000: 121
Как: 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); 
    } 
} 
+8

Почему бы не посмотреть на jitted-код? Даже отладчик VS может показать это. –

+2

Мне просто интересно, вы тоже проверили CLR 4.0? –

+1

@ Антон: Хорошая точка. Сделаю в какой-то момент (хотя в настоящий момент это не в VS :) @divo: Да, и все хуже. Но тогда это в бета-версии, поэтому там может быть много отладочного кода. –

ответ

183

Очевидно, что машинный код, который может генерировать JIT-компилятор для первого случая, намного эффективнее. Одно правило, которое действительно помогает, состоит в том, что объект может быть только unboxed для переменной, которая имеет тот же тип, что и размер в штучной упаковке. Это позволяет компилятору JIT генерировать очень эффективный код, и никакие конверсии значений не должны учитываться.

is Эксплуатационный тест прост, просто проверьте, не является ли объект нулевым и имеет ожидаемый тип, принимает только несколько инструкций машинного кода. Литье также легко, компилятор JIT знает расположение битов значения в объекте и использует их напрямую. Никакое копирование или преобразование не происходит, весь машинный код является встроенным и занимает всего около десятка инструкций. Это должно было быть действительно эффективным в .NET 1.0, когда бокс был обычным явлением.

Кастинг для int? занимает гораздо больше работы. Представление значения целого числа в штучной упаковке несовместимо с макетом памяти Nullable<int>. Требуется преобразование, и код является сложным из-за возможных типов переименованных коробок. Компилятор JIT генерирует вызов вспомогательной функции CLR с именем JIT_Unbox_Nullable, чтобы выполнить задание. Это функция общего назначения для любого типа значений, много кода для проверки типов. И значение копируется. Трудно оценить стоимость, так как этот код заблокирован внутри mscorwks.dll, но, вероятно, сотни инструкций машинного кода.

Метод расширения Linq OfType() также использует is оператор и литой. Это, однако, приведение к родовому типу. Компилятор JIT генерирует вызов вспомогательной функции JIT_Unbox(), которая может выполнять приведение к произвольному типу значений. У меня нет отличного объяснения, почему оно так медленно, как литье, до Nullable<int>, учитывая, что нужно меньше работать. Я подозреваю, что ngen.exe может вызвать проблемы.

+14

Хорошо, я убежден. Думаю, я привык думать о том, что «есть», как потенциально дорого, из-за возможности идти вверх по иерархии наследования, но в случае типа значения нет возможности иерархии, поэтому это может быть просто побитовое сравнение , Я все еще думаю, что JIT-код для случая с нулевым значением может быть оптимизирован JIT намного тяжелее, чем есть. –

8

у меня нет времени, чтобы попробовать это, но вы можете иметь:

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

в

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

Вы создаете новый объект каждый раз, что не будет полностью объяснить проблема, но может способствовать.

+0

Я пробовал, но это, похоже, мало влияет ... –

+1

Нет, я побежал, и он немного медленнее. –

+1

Объявление переменной в другом месте значительно влияет на сгенерированный код значительно при захвате переменной (в какой момент это влияет на фактическую семантику) в моем опыте. Обратите внимание, что он не создает новый объект в куче, хотя он, безусловно, создает новый экземпляр 'int?' В стеке, используя 'unbox.any'. Я подозреваю, что в этом проблема - я думаю, что ручной IL мог бы обойти оба варианта здесь ... хотя также возможно, что JIT оптимизирован для распознавания для случая is/cast и только один раз проверяет. –

23

Мне кажется, что isinst просто очень медленный по типам с нулевым значением. В методе FindSumWithCast я изменил

if (o is int) 

в

if (o is int?) 

который также значительно замедляет выполнение. Только разностных в IL я могу видеть, что

isinst  [mscorlib]System.Int32 

получает изменено на

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

Это больше, чем это; в «литом» случае за «isinst» следует тест для nullity, а затем * условно * 'unbox.any'. В случае с нулевым значением есть * безусловный * 'unbox.any'. –

+0

Да, получается ** оба ** 'isinst' и' unbox.any' медленнее по типам с нулевым значением. –

+0

@Jon: Вы можете просмотреть мой ответ о том, зачем нужен актерский состав. (Я знаю, что это старо, но я только что обнаружил это q и думал, что должен предоставить свои 2c из того, что я знаю о CLR). –

19

Интересно, что я прошел на обратной связи о поддержке оператора через dynamic быть порядка величины равна медленнее Nullable<T> (аналогично до this early test) - Я подозреваю, что по очень похожим причинам.

Gotta love Nullable<T>.Еще одно удовольствие, что даже несмотря на то, JIT пятна (и удаляет) null для ненулевых структур, она borks его Nullable<T>:

using System; 
using System.Diagnostics; 
static class Program { 
    static void Main() { 
     // JIT 
     TestUnrestricted<int>(1,5); 
     TestUnrestricted<string>("abc",5); 
     TestUnrestricted<int?>(1,5); 
     TestNullable<int>(1, 5); 

     const int LOOP = 100000000; 
     Console.WriteLine(TestUnrestricted<int>(1, LOOP)); 
     Console.WriteLine(TestUnrestricted<string>("abc", LOOP)); 
     Console.WriteLine(TestUnrestricted<int?>(1, LOOP)); 
     Console.WriteLine(TestNullable<int>(1, LOOP)); 

    } 
    static long TestUnrestricted<T>(T x, int loop) { 
     Stopwatch watch = Stopwatch.StartNew(); 
     int count = 0; 
     for (int i = 0; i < loop; i++) { 
      if (x != null) count++; 
     } 
     watch.Stop(); 
     return watch.ElapsedMilliseconds; 
    } 
    static long TestNullable<T>(T? x, int loop) where T : struct { 
     Stopwatch watch = Stopwatch.StartNew(); 
     int count = 0; 
     for (int i = 0; i < loop; i++) { 
      if (x != null) count++; 
     } 
     watch.Stop(); 
     return watch.ElapsedMilliseconds; 
    } 
} 
+0

Yowser. Это очень тяжелая разница. Ик. –

+0

Следовательно, некоторые из неясного кода в MiscUtil/Operator ;-p –

+0

Если из всего этого не вышло никакого другого товара, это привело меня к включению предупреждений как для моего исходного кода *, так и для этого: –

7
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 

[EDIT: 2010-06-19]

Примечание: предыдущий тест выполнялся внутри VS, отладка конфигурации, используя VS2009, используя Core i7 (compan y машина разработки).

Следующая было сделано на моей машине с помощью 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 
+0

Какую версию рамки вы используете, из интереса? Результаты на моем нетбуке (с использованием .NET 4RC) еще более драматичны - версии, использующие As, намного хуже, чем ваши результаты. Возможно, они улучшили его для RTF .NET 4? Я все еще думаю, что это может быть быстрее ... –

+0

Я использую .NET 3.5 Framework –

+0

@Michael: Вы используете неоптимизированную сборку или работаете в отладчике? –

8

Я попытался точно проверить тип построения

typeof(int) == item.GetType(), который выполняет так же быстро, как версия item is int, и всегда возвращается номер (выделение: даже если вы написали массив Nullable<int>, вам нужно будет использовать typeof(int)). Вам также понадобится дополнительно null != item здесь.

Однако

typeof(int?) == item.GetType() остается быстрым (в отличие от item is int?), но всегда возвращает ложь.

Тип-конструкция в моих глазах самый быстрый способ для точный тип проверки, так как он использует RuntimeTypeHandle. Поскольку точные типы в этом случае не совпадают с нулевыми значениями, я полагаю, is/as должны сделать дополнительную тяжелую работу здесь, чтобы убедиться, что это фактически экземпляр типа Nullable.

И, честно говоря: что делает ваш is Nullable<xxx> plus HasValue? Ничего. Вы всегда можете перейти непосредственно к базовому (значению) типу (в данном случае). Вы либо получаете значение, либо «нет, а не экземпляр типа, о котором вы просите». Даже если вы написали (int?)null в массив, проверка типа вернет false.

+0

Интересно ... идея использования «как» + HasValue (не * is * plus HasValue, note) заключается в том, что он выполняет только проверку типа * один раз * вместо двух. Он делает «check and unbox» за один шаг. Это похоже на то, что он должен быть быстрее ... но это явно не так. Я не уверен, что вы подразумеваете под последним предложением, но нет такой вещи, как boxed 'int?' - если вы выберете значение 'int? ', Оно будет завершено в виде вставки в ячейку или ссылку« null ». –

12

Это является результатом FindSumWithAsAndHas выше: alt text http://www.freeimagehosting.net/uploads/9e3c0bfb75.png

Это является результатом FindSumWithCast: alt text http://www.freeimagehosting.net/uploads/ce8a5a3934.png

Выводы:

  • Используя as, это проверить сначала, если объект является экземпляром Int32; под капотом используется isinst Int32 (что похоже на рукописный код: if (o is int)). И используя as, он также безоговорочно распакует объект. И это реальная производительность убийца назвать свойство (это по-прежнему функционирует под капотом), IL_0027

  • Используя бросок, вы проверяете первый, если объект является intif (o is int); под капотом это используется isinst Int32.Если это экземпляр междунар, то вы смело можете распаковывать значение, IL_002D

Проще говоря, это псевдо-код, используя as подход:

int? x; 

(x.HasValue, x.Value) = (o isinst Int32, o unbox Int32) 

if (x.HasValue) 
    sum += x.Value;  

И это псевдо -код с помощью произнесения подхода:

if (o isinst Int32) 
    sum += (o unbox Int32) 

Так литой ((int)a[i], а синтаксис выглядит как литой, но на самом деле это распаковка, литые и распаковка один и тот же синтаксис, ряд т ime, я буду педантичным с правильной терминологией), подход действительно быстрее, вам нужно только удалить значение, когда объект определенно int. То же самое нельзя сказать об использовании подхода as.

9

Профилирование далее:

using System; 
using System.Diagnostics; 

class Program 
{ 
    const int Size = 30000000; 

    static void Main(string[] args) 
    { 
     object[] values = new object[Size]; 
     for (int i = 0; i < Size - 2; i += 3) 
     { 
      values[i] = null; 
      values[i + 1] = ""; 
      values[i + 2] = 1; 
     } 

     FindSumWithIsThenCast(values); 

     FindSumWithAsThenHasThenValue(values); 
     FindSumWithAsThenHasThenCast(values); 

     FindSumWithManualAs(values); 
     FindSumWithAsThenManualHasThenValue(values); 



     Console.ReadLine(); 
    } 

    static void FindSumWithIsThenCast(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("Is then Cast: {0} : {1}", sum, 
          (long)sw.ElapsedMilliseconds); 
    } 

    static void FindSumWithAsThenHasThenValue(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 then Has then Value: {0} : {1}", sum, 
          (long)sw.ElapsedMilliseconds); 
    } 

    static void FindSumWithAsThenHasThenCast(object[] values) 
    { 
     Stopwatch sw = Stopwatch.StartNew(); 
     int sum = 0; 
     foreach (object o in values) 
     { 
      int? x = o as int?; 

      if (x.HasValue) 
      { 
       sum += (int)o; 
      } 
     } 
     sw.Stop(); 
     Console.WriteLine("As then Has then Cast: {0} : {1}", sum, 
          (long)sw.ElapsedMilliseconds); 
    } 

    static void FindSumWithManualAs(object[] values) 
    { 
     Stopwatch sw = Stopwatch.StartNew(); 
     int sum = 0; 
     foreach (object o in values) 
     { 
      bool hasValue = o is int; 
      int x = hasValue ? (int)o : 0; 

      if (hasValue) 
      { 
       sum += x; 
      } 
     } 
     sw.Stop(); 
     Console.WriteLine("Manual As: {0} : {1}", sum, 
          (long)sw.ElapsedMilliseconds); 
    } 

    static void FindSumWithAsThenManualHasThenValue(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 then Manual Has then Value: {0} : {1}", sum, 
          (long)sw.ElapsedMilliseconds); 
    } 

} 

Выход:

Is then Cast: 10000000 : 303 
As then Has then Value: 10000000 : 3524 
As then Has then Cast: 10000000 : 3272 
Manual As: 10000000 : 395 
As then Manual Has then Value: 10000000 : 3282 

Что мы можем сделать вывод из этих цифр?

  • Во-первых, это-то отливка подход значительно быстрее, чем , как подход. 303 против 3524
  • Во-вторых, значение незначительно медленнее, чем литье. 3524 vs 3272
  • В-третьих, .HasValue немного медленнее, чем при использовании ручного управления (то есть с использованием -).3524 против 3282
  • В-четвертых, делает яблоко-на-яблока сравнения (то есть и присвоившей моделируемой HasValue и преобразующего моделируемой Value происходит вместе) между имитируется, как и реального как подход, мы можем видеть моделируется как является все еще значительно быстрее, чем реальный как. 395 против 3524
  • Наконец, на основе первого и четвертого заключение отметим, что что-то не так с в реализации^_^
22

Это первоначально начиналось как комментарий к отличным ответ Ганса Passant, но он получил слишком поэтому я хочу добавить несколько бит здесь:

Во-первых, оператор C# as испускает команду IL-isinst (также оператор is). (Еще одна интересная инструкция castclass, emited, когда вы делаете прямой бросок и компилятор знает, что проверка выполнения не может быть опущен.)

Вот что isinst делает (ECMA 335 Partition III, 4.6):

Формат: isinsttypeTok

typeTok является маркером метаданных (а typeref, typedef или typespec), с указанием желаемого класса.

Если typeTok представляет собой тип ненулевое значение или общий тип параметра он интерпретируется как «коробочного» typeTok.

Если typeTok является обнуляемым типа, Nullable<T>, он интерпретируется как «коробочный» T

Самое главное:

Если фактический тип (не верификатор отслеживаются типа) obj is verifier-assignable-to тип typeTok затем isinst преуспевает и obj (как результат) возвращается без изменений, в то время как проверка отслеживает его тип как typeTok. В отличие от принуждений (§1.6) и преобразований (§3.27), isinst никогда не изменяет фактический тип объекта и не сохраняет идентичность объекта (см. Раздел I).

Таким образом, убийца производительность не isinst в этом случае, но дополнительный unbox.any. Это не было ясно из ответа Ганса, поскольку он смотрел только на код JITed. В общем случае компилятор C# выдаст unbox.any после isinst T? (но опустит его в случае, если вы делаете isinst T, когда T является ссылочным типом).

Почему это так? isinst T? никогда не имеет эффекта, который был бы очевиден, т. Е. Вы вернете T?. Вместо этого все эти инструкции гарантируют, что у вас есть "boxed T", который можно распаковать до T?. Чтобы получить фактический T?, нам по-прежнему необходимо удалить наш "boxed T" в T?, поэтому компилятор испускает unbox.any после isinst. Если вы думаете об этом, это имеет смысл, потому что «формат окна» для T? - это всего лишь "boxed T", и делая castclass и isinst, выполнение unbox будет непоследовательным.

Резервное копирование нахождение Ганса с некоторой информацией из standard, здесь идет:

(ECMA 335 Раздел III, 4.33): unbox.any

При применении к коробочной форме типа значения , команда unbox.any извлекает значение, содержащееся в obj (типа O). (Это эквивалентно unbox, за которым следует ldobj.) При применении к эталонному типу команда unbox.any имеет тот же эффект, что и castclass typeTok.

(ECMA 335 Раздел III, 4.32): unbox

Как правило, unbox просто вычисляет адрес типа значения, который уже присутствует внутри коробочного объекта. Такой подход невозможен при распаковке типов значений NULL. Поскольку значения Nullable<T> преобразуются в коробку Ts во время операции с коробкой, реализация часто должна производить новый Nullable<T> в куче и вычислять адрес для вновь выделенного объекта.

3

Для того, чтобы сохранить этот ответ уточненный, стоит отметить, что большая часть дискуссий на этой странице сейчас спорный теперь C# 7.1 и .NET 4.7 который поддерживает тонкий синтаксис который также создает лучший IL-код. Оригинальный пример

Ор в ...

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 тип значения (т.е. struct в C#), который реализует IEquatable<MyStruct> (как и большинство).После реализации строго типизированных Equals(MyStruct other) метод, вы можете теперь корректно перенаправлять нетипизированное Equals(Object obj) переопределение (унаследованный от Object) к ней следующим образом:

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

 


Приложение:Release сборки IL Здесь приводятся коды первых двух примерных функций, показанных выше в этом ответе (соответственно). В то время как новый синтаксис дает немного меньший IL-код, он в основном выигрывает большой, делая нулевые вызовы (против двух) и избегая при этом операции unbox, когда это возможно.

// 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 синтаксиса превосходящего ранее доступные параметры, см here (в частности, например, «D») ,