2009-03-26 2 views
81

У меня есть программа, требующая быстрой работы. Внутри одного из своих внутренних циклов мне нужно проверить тип объекта, чтобы узнать, наследуется ли он от определенного интерфейса.C# 'оператор'

Один из способов сделать это будет с встроенной функцией проверки типа CLR. Самый элегантный метод там, вероятно, является «это» ключевое слово:

if (obj is ISpecialType) 

Другой подход должен был бы дать свою собственную функцию виртуального GetType(), которая возвращает предопределенный значение перечисления (в моем случае это базовый класс, на самом деле , мне нужен только bool). Этот метод был бы быстрым, но менее элегантным.

Я слышал, что есть инструкция IL специально для ключевого слова 'is', но это не значит, что она выполняется быстро при переходе на встроенную сборку. Может ли кто-нибудь рассказать о производительности «is» по сравнению с другим методом?

ОБНОВЛЕНИЕ: Благодарим за все обоснованные ответы! Кажется, в ответах есть несколько полезных моментов: точка Андрея, касающаяся «есть», автоматически выполняет бросок, но данные производительности, собранные Binary Worrier и Ian, также чрезвычайно полезны. Было бы здорово, если бы один из ответов был отредактирован, чтобы включить все этой информации.

+2

кстати, CLR не даст вам возможность создать свою собственную функцию Type GetType(), так как он нарушает одно из основных правил CLR - поистине типы – abatishchev

+1

Er, я не совсем уверен, что вы подразумеваете под правилом «действительно типы», но я понимаю, что CLR имеет встроенную функцию Type GetType(). Если бы я использовал этот метод, это было бы с функцией другого имени, возвращающей некоторое перечисление, поэтому не было бы конфликта имен и символов. – JubJub

+3

Я думаю, что абатищев означал «тип безопасности». GetType() не является виртуальным, чтобы препятствовать тому, чтобы тип лежал о себе и, следовательно, сохранял безопасность типов. –

ответ

99

Использование is может повредить производительность, если, как только вы проверите тип, вы примените его к этому типу. is фактически переводит объект в тип, который вы проверяете, поэтому любое последующее литье является избыточным.

Если вы собираетесь бросить в любом случае, здесь лучший подход:

ISpecialType t = obj as ISpecialType; 

if (t != null) 
{ 
    // use t here 
} 
+0

Спасибо. Но если я не собираюсь бросать объект, если условие терпит неудачу, я бы лучше использовал виртуальную функцию для проверки типа вместо этого? – JubJub

+4

@JubJub: нет. Неисправность 'as' в основном выполняет ту же операцию, что и' is' (а именно, проверка типа). Единственное различие заключается в том, что он затем возвращает 'null' вместо' false'. –

15

Эндрю правильно. Фактически с анализом кода это получает сообщение Visual Studio как ненужный приведение.

Одна идея (не зная, что вы делаете, это немного выстрел в темноте), но мне всегда рекомендуется избегать таких проверок и вместо этого иметь другой класс. Поэтому вместо выполнения некоторых проверок и различных действий в зависимости от типа, класс должен знать, как обрабатывать себя ...

e.g. Obj может быть ISpecialType или IType;

оба имеют метод DoStuff(). Для IType он может просто возвращаться или выполнять пользовательские вещи, тогда как ISpecialType может делать другие вещи.

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

+0

Да, поскольку все, что я собираюсь сделать, если тип test true вызывает на нем определенный метод интерфейса, я мог бы просто переместить этот метод интерфейса в базовый класс и заставить его ничего не делать по умолчанию. Это может быть более элегантным, чем создание виртуальной функции для тестирования типа. – JubJub

+0

Я сделал аналогичный тест на Binary Worrier после комментариев Абатищева и нашел только разницу в 60 мс за 10 000 000 итераций. – Ian

+1

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

64

Я с Ian, вы, вероятно, не хотите этого делать.

Однако, только так вы знаете, есть очень маленькая разница между этими двумя, более 10000000 итераций

  • чеке перечисление приходит на миллисекунды (приблизительно)
  • ИС проверка приходит в 1000 на миллисекунды (приблизительно)

лично я бы не решить эту проблему, е это способ, но если бы я был вынужден выбрать один метод, это был бы встроенный контроль IS, разница в производительности не стоит рассматривать накладные расходы на кодирование.

Моя база и производные классы

class MyBaseClass 
{ 
    public enum ClassTypeEnum { A, B } 
    public ClassTypeEnum ClassType { get; protected set; } 
} 

class MyClassA : MyBaseClass 
{ 
    public MyClassA() 
    { 
     ClassType = MyBaseClass.ClassTypeEnum.A; 
    } 
} 
class MyClassB : MyBaseClass 
{ 
    public MyClassB() 
    { 
     ClassType = MyBaseClass.ClassTypeEnum.B; 
    } 
} 

JubJub: В соответствии с просьбой больше информации о тестах.

Я побежал оба теста из консольного приложения (отладки сборки) каждый тест выглядит следующим образом

static void IsTest() 
{ 
    DateTime start = DateTime.Now; 
    for (int i = 0; i < 10000000; i++) 
    { 
     MyBaseClass a; 
     if (i % 2 == 0) 
      a = new MyClassA(); 
     else 
      a = new MyClassB(); 
     bool b = a is MyClassB; 
    } 
    DateTime end = DateTime.Now; 
    Console.WriteLine("Is test {0} miliseconds", (end - start).TotalMilliseconds); 
} 

Запуск в выпуске, я получаю разницу в 60 - 70 мс, как Ян.

Дальнейшее обновление - 25 октября 2012
Через пару лет от я заметил кое-что об этом, компилятор может выбрать опустить bool b = a is MyClassB в освобождении, потому что б не используется в любом месте.

Этот код. , ,

public static void IsTest() 
{ 
    long total = 0; 
    var a = new MyClassA(); 
    var b = new MyClassB(); 
    var sw = new Stopwatch(); 
    sw.Start(); 
    for (int i = 0; i < 10000000; i++) 
    { 
     MyBaseClass baseRef; 
     if (i % 2 == 0) 
      baseRef = a;//new MyClassA(); 
     else 
      baseRef = b;// new MyClassB(); 
     //bool bo = baseRef is MyClassB; 
     bool bo = baseRef.ClassType == MyBaseClass.ClassTypeEnum.B; 
     if (bo) total += 1; 
    } 
    sw.Stop(); 
    Console.WriteLine("Is test {0} miliseconds {1}", sw.ElapsedMilliseconds, total); 
} 

. , , последовательно показывает, что проверка is происходит примерно через 57 миллисекунд, а сравнение переходов - 29 миллисекунд.

NBЯ по-прежнему предпочитают is чек, разница слишком мала, чтобы заботиться о

+28

+1 для фактического тестирования производительности вместо предположения. –

+0

Спасибо за тестирование! Ян прокомментировал выше, что он провел тест производительности, и разница между методами была даже более слабой, чем то, что вы нашли. Мне любопытно, как вы пришли к своим номерам. – JubJub

+0

Спасибо за обновление! Если я правильно понимаю, ваш второй тест аннулирует более ранние результаты 700 против 1000 мс. Возможно, вам следует обновить сообщение, чтобы отразить это, и включить ответ Эндрю, чтобы сделать этот наиболее информативный ответ. – JubJub

20

Ok поэтому я болтал об этом с кем-то и решил проверить это больше. Насколько я могу судить, производительность as и is очень хороша, по сравнению с тестированием вашего собственного члена или функции для хранения информации о типе.

Я использовал Stopwatch, что я только что узнал, возможно, не самый надежный подход, поэтому я также попробовал UtcNow. Позже я также попробовал подход к процессорному времени, который похож на UtcNow, включая непредсказуемое время создания. Я также попытался сделать базовый класс не абстрактным без виртуальных, но он, похоже, не оказал значительного эффекта.

Я запускал это на Quad Q6600 с 16 ГБ оперативной памяти. Даже с итерациями 50 мил, цифры все еще падают вокруг +/- 50 или около того миллисекунда, поэтому я не буду слишком много читать в незначительных различиях.

Это было интересно посмотреть, что x64 создан быстрее, но выполняется как/медленнее, чем x86

64 Режим съемки:
Секундомер:
Как: 561ms
Is: 597ms
База недвижимости: поле 539ms
Основание: 555ms
База RO поле: 552ms
Virtual GetEnumType() тест: 556ms
Virtual ISB() Тест: 588ms
Время создания: 10416ms

UtcNow:
Как: 499ms
Is: 532ms
База недвижимости: 479ms
Базовое поле: 502ms
База RO поле: 491ms
Virtual GetEnumType(): 502ms
Virtual BOOL ISB(): 522ms
Время создания: 285ms (Это число кажется ненадежной остроумие h UtcNow. Я также получаю 109ms и 806ms)

x86 Режим выхода:.
Секундомер:
Как: 391ms
Is: 423ms
База недвижимости: 369ms
Базовое поле: 321ms
База RO поле : 339ms
Виртуальный тест GetEnumType(): 361ms
Virtual IsB() test: 365ms
Время создания: 14106ms

UtcNow:
Как: 348ms
Is: 375ms
База недвижимости: 329ms
Базовое поле: 286ms
База RO поле: 309ms
Virtual GetEnumType(): 321ms
Virtual BOOL ISB (): 332ms
Время создания: 544ms (Это число кажется ненадежным с помощью UtcNow.)

Вот большая часть кода:

static readonly int iterations = 50000000; 
    void IsTest() 
    { 
     Process.GetCurrentProcess().ProcessorAffinity = (IntPtr)1; 
     MyBaseClass[] bases = new MyBaseClass[iterations]; 
     bool[] results1 = new bool[iterations]; 

     Stopwatch createTime = new Stopwatch(); 
     createTime.Start(); 
     DateTime createStart = DateTime.UtcNow; 
     for (int i = 0; i < iterations; i++) 
     { 
      if (i % 2 == 0) bases[i] = new MyClassA(); 
      else bases[i] = new MyClassB(); 
     } 
     DateTime createStop = DateTime.UtcNow; 
     createTime.Stop(); 


     Stopwatch isTimer = new Stopwatch(); 
     isTimer.Start(); 
     DateTime isStart = DateTime.UtcNow; 
     for (int i = 0; i < iterations; i++) 
     { 
      results1[i] = bases[i] is MyClassB; 
     } 
     DateTime isStop = DateTime.UtcNow; 
     isTimer.Stop(); 
     CheckResults(ref results1); 

     Stopwatch asTimer = new Stopwatch(); 
     asTimer.Start(); 
     DateTime asStart = DateTime.UtcNow; 
     for (int i = 0; i < iterations; i++) 
     { 
      results1[i] = bases[i] as MyClassB != null; 
     } 
     DateTime asStop = DateTime.UtcNow; 
     asTimer.Stop(); 
     CheckResults(ref results1); 

     Stopwatch baseMemberTime = new Stopwatch(); 
     baseMemberTime.Start(); 
     DateTime baseStart = DateTime.UtcNow; 
     for (int i = 0; i < iterations; i++) 
     { 
      results1[i] = bases[i].ClassType == MyBaseClass.ClassTypeEnum.B; 
     } 
     DateTime baseStop = DateTime.UtcNow; 
     baseMemberTime.Stop(); 
     CheckResults(ref results1); 

     Stopwatch baseFieldTime = new Stopwatch(); 
     baseFieldTime.Start(); 
     DateTime baseFieldStart = DateTime.UtcNow; 
     for (int i = 0; i < iterations; i++) 
     { 
      results1[i] = bases[i].ClassTypeField == MyBaseClass.ClassTypeEnum.B; 
     } 
     DateTime baseFieldStop = DateTime.UtcNow; 
     baseFieldTime.Stop(); 
     CheckResults(ref results1); 


     Stopwatch baseROFieldTime = new Stopwatch(); 
     baseROFieldTime.Start(); 
     DateTime baseROFieldStart = DateTime.UtcNow; 
     for (int i = 0; i < iterations; i++) 
     { 
      results1[i] = bases[i].ClassTypeField == MyBaseClass.ClassTypeEnum.B; 
     } 
     DateTime baseROFieldStop = DateTime.UtcNow; 
     baseROFieldTime.Stop(); 
     CheckResults(ref results1); 

     Stopwatch virtMethTime = new Stopwatch(); 
     virtMethTime.Start(); 
     DateTime virtStart = DateTime.UtcNow; 
     for (int i = 0; i < iterations; i++) 
     { 
      results1[i] = bases[i].GetClassType() == MyBaseClass.ClassTypeEnum.B; 
     } 
     DateTime virtStop = DateTime.UtcNow; 
     virtMethTime.Stop(); 
     CheckResults(ref results1); 

     Stopwatch virtMethBoolTime = new Stopwatch(); 
     virtMethBoolTime.Start(); 
     DateTime virtBoolStart = DateTime.UtcNow; 
     for (int i = 0; i < iterations; i++) 
     { 
      results1[i] = bases[i].IsB(); 
     } 
     DateTime virtBoolStop = DateTime.UtcNow; 
     virtMethBoolTime.Stop(); 
     CheckResults(ref results1); 


     asdf.Text += 
     "Stopwatch: " + Environment.NewLine 
      + "As: " + asTimer.ElapsedMilliseconds + "ms" + Environment.NewLine 
      +"Is: " + isTimer.ElapsedMilliseconds + "ms" + Environment.NewLine 
      + "Base property: " + baseMemberTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Base field: " + baseFieldTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Base RO field: " + baseROFieldTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Virtual GetEnumType() test: " + virtMethTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Virtual IsB() test: " + virtMethBoolTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Create Time : " + createTime.ElapsedMilliseconds + "ms" + Environment.NewLine + Environment.NewLine+"UtcNow: " + Environment.NewLine + "As: " + (asStop - asStart).Milliseconds + "ms" + Environment.NewLine + "Is: " + (isStop - isStart).Milliseconds + "ms" + Environment.NewLine + "Base property: " + (baseStop - baseStart).Milliseconds + "ms" + Environment.NewLine + "Base field: " + (baseFieldStop - baseFieldStart).Milliseconds + "ms" + Environment.NewLine + "Base RO field: " + (baseROFieldStop - baseROFieldStart).Milliseconds + "ms" + Environment.NewLine + "Virtual GetEnumType(): " + (virtStop - virtStart).Milliseconds + "ms" + Environment.NewLine + "Virtual bool IsB(): " + (virtBoolStop - virtBoolStart).Milliseconds + "ms" + Environment.NewLine + "Create Time : " + (createStop-createStart).Milliseconds + "ms" + Environment.NewLine; 
    } 
} 

abstract class MyBaseClass 
{ 
    public enum ClassTypeEnum { A, B } 
    public ClassTypeEnum ClassType { get; protected set; } 
    public ClassTypeEnum ClassTypeField; 
    public readonly ClassTypeEnum ClassTypeReadonlyField; 
    public abstract ClassTypeEnum GetClassType(); 
    public abstract bool IsB(); 
    protected MyBaseClass(ClassTypeEnum kind) 
    { 
     ClassTypeReadonlyField = kind; 
    } 
} 

class MyClassA : MyBaseClass 
{ 
    public override bool IsB() { return false; } 
    public override ClassTypeEnum GetClassType() { return ClassTypeEnum.A; } 
    public MyClassA() : base(MyBaseClass.ClassTypeEnum.A) 
    { 
     ClassType = MyBaseClass.ClassTypeEnum.A; 
     ClassTypeField = MyBaseClass.ClassTypeEnum.A;    
    } 
} 
class MyClassB : MyBaseClass 
{ 
    public override bool IsB() { return true; } 
    public override ClassTypeEnum GetClassType() { return ClassTypeEnum.B; } 
    public MyClassB() : base(MyBaseClass.ClassTypeEnum.B) 
    { 
     ClassType = MyBaseClass.ClassTypeEnum.B; 
     ClassTypeField = MyBaseClass.ClassTypeEnum.B; 
    } 
} 
+30

(бонусный 5 am-inspired Шекспир ...) Быть или не быть: вот в чем вопрос: ли «ТИС благороднее в коде страдать перечислений и свойства абстрактных баз, Или взять предложения промежуточного лингвиста И, ссылаясь на его инструкцию, доверяйте им? Угадать: удивляться; Не более; и по времени, чтобы различить, мы заканчиваем головную боль и тысячи подсознательных заданий , что временные кодировщики являются наследниками. «Это закрытие Желательно, чтобы вы пожелали.Чтобы умереть, нет, но спать; Да, я буду спать, возможно, мечтать о и как в том, что может быть получено из самой базы класса. –

+0

Можем ли мы заключить, что доступ к свойству быстрее на x64, а затем доступ к полю !!! Потому что это для меня неожиданность, как это может быть? –

+0

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

13

Я сделал сравнительная производительность на две возможности сравнения типа

  1. MyObject.GetType() == TypeOf (MyClass)
  2. MyObject является MyClass

Результат: Использование "это" примерно в 10 раз быстрее !!!

Выход:

Время для Type-сравнения: 00: 00: 00,456

Время Is-сравнения: 00: 00: 00,042

Мой код:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Diagnostics; 

namespace ConsoleApplication3 
{ 
    class MyClass 
    { 
     double foo = 1.23; 
    } 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      MyClass myobj = new MyClass(); 
      int n = 10000000; 

      Stopwatch sw = Stopwatch.StartNew(); 

      for (int i = 0; i < n; i++) 
      { 
       bool b = myobj.GetType() == typeof(MyClass); 
      } 

      sw.Stop(); 
      Console.WriteLine("Time for Type-Comparison: " + GetElapsedString(sw)); 

      sw = Stopwatch.StartNew(); 

      for (int i = 0; i < n; i++) 
      { 
       bool b = myobj is MyClass; 
      } 

      sw.Stop(); 
      Console.WriteLine("Time for Is-Comparison: " + GetElapsedString(sw)); 
     } 

     public static string GetElapsedString(Stopwatch sw) 
     { 
      TimeSpan ts = sw.Elapsed; 
      return String.Format("{0:00}:{1:00}:{2:00}.{3:000}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds); 
     } 
    } 
} 
-3

Мне всегда рекомендуется избегать таких проверок, tead есть другой класс. Поэтому вместо выполнения некоторых проверок и различных действий в зависимости от типа, класс должен знать, как обрабатывать себя ...

e.g. Obj может быть ISpecialType или IType;

оба имеют метод DoStuff(). Для IType он может просто возвращаться или выполнять пользовательские вещи, тогда как ISpecialType может делать другие вещи.

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

3

Пойнт Andrew Hare сделал об исполнении потерянного при выполнении is чека и затем отливал был действителен, но в C# 7.0 мы можем сделать, это матч проверки ведьмы шаблон, чтобы избежать дополнительного бросания позже:

if (obj is ISpecialType st) 
{ 
    //st is in scope here and can be used 
} 

Более того, если вам нужно проверить между несколькими типами C# 7.0 шаблона соответствия конструкции в настоящее время позволяет делать switch по типам:

public static double ComputeAreaModernSwitch(object shape) 
{ 
    switch (shape) 
    { 
     case Square s: 
      return s.Side * s.Side; 
     case Circle c: 
      return c.Radius * c.Radius * Math.PI; 
     case Rectangle r: 
      return r.Height * r.Length; 
     default: 
      throw new ArgumentException(
       message: "shape is not a recognized shape", 
       paramName: nameof(shape)); 
    } 
} 

вы можете прочитать больше о шаблоне в C# в документации here ,

0

В случае, если кто-то задается вопросом, я провел тесты в Unity engine 2017.1 со сценарием версии .NET .NET 6.5 (Experimantal) на ноутбуке с процессором i5-4200U. Результаты:

Average Relative To Local Call LocalCall 117.33 1.00 is 241.67 2.06 Enum 139.33 1.19 VCall 294.33 2.51 GetType 276.00 2.35

Полный текст статьи: http://www.ennoble-studios.com/tuts/unity-c-performance-comparison-is-vs-enum-vs-virtual-call.html

Смежные вопросы