2009-03-26 15 views
81

Ho un programma che richiede prestazioni veloci. All'interno di uno dei suoi loop interni, ho bisogno di testare il tipo di un oggetto per vedere se eredita da una determinata interfaccia.C# 'è' prestazione dell'operatore

Un modo per farlo sarebbe con la funzionalità di controllo del tipo integrata del CLR. Il metodo più elegante c'è probabilmente il 'è' parola chiave:

if (obj is ISpecialType) 

Un altro approccio sarebbe quello di dare alla classe di base la mia propria funzione GetType virtuale(), che restituisce un valore enum predefinito (nel mio caso, in realtà , ho solo bisogno di un bool). Quel metodo sarebbe veloce, ma meno elegante.

Ho sentito che esiste un'istruzione IL specifica per la parola chiave 'is', ma ciò non significa che venga eseguita rapidamente quando viene tradotta in assembly nativo. Qualcuno può condividere alcune informazioni sulle prestazioni di "è" rispetto all'altro metodo?

AGGIORNAMENTO: Grazie per tutte le risposte informate! Sembra che un paio di punti utili siano distribuiti tra le risposte: il punto di Andrew su "è" che esegue automaticamente un cast è essenziale, ma anche i dati sulle prestazioni raccolti da Binary Worrier e Ian sono estremamente utili. Sarebbe bello se una delle risposte fosse modificata per includere tutte di queste informazioni.

+2

btw, CLR non vi darà la possibilità di creare la propria funzione Tipo GetType(), perché rompe una delle regole CLR principali - veramente tipi – abatishchev

+1

Ehm, non sono completamente sicuro di cosa intendi con la regola dei "veri tipi", ma capisco che il CLR ha una funzione Type GetType() incorporata. Se dovessi usare quel metodo, sarebbe con una funzione di un nome diverso che restituisce qualche enum, quindi non ci sarebbe alcun conflitto di nome/simbolo. – JubJub

+3

Penso che abatishchev significhi "tipo sicurezza". GetType() non è virtuale per impedire a un tipo di mentire su se stesso e quindi preservare la sicurezza del tipo. –

risposta

99

L'utilizzo di is può compromettere le prestazioni se, una volta verificato il tipo, si esegue il cast di quel tipo. is in realtà esegue il cast dell'oggetto al tipo che si sta verificando, in modo che ogni casting successivo sia ridondante.

Se avete intenzione di lanciare in ogni caso, ecco un approccio migliore:

ISpecialType t = obj as ISpecialType; 

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

Grazie. Ma se non ho intenzione di lanciare l'oggetto se il condizionale fallisce, sarebbe meglio usare una funzione virtuale per testare il tipo? – JubJub

+4

@JubJub: no. Un errore 'as' fondamentalmente esegue la stessa operazione di' is' (ovvero, il controllo del tipo). L'unica differenza è che restituisce "null" invece di "false". –

15

Andrew è corretta. Infatti con l'analisi del codice questo viene segnalato da Visual Studio come un cast non necessario.

Un'idea (senza sapere cosa si sta facendo è un po 'sparo al buio), ma mi è sempre stato consigliato di evitare il controllo in questo modo, e invece di avere un'altra classe. Quindi, piuttosto che fare alcuni controlli e avere azioni diverse a seconda del tipo, fare in modo che la classe sappia come elaborarsi ...

ad es. Obj può essere ISpecialType o IType;

entrambi hanno un metodo DoStuff() definito. Per IType può solo restituire o fare cose personalizzate, mentre ISpecialType può fare altre cose.

Questo rimuove completamente qualsiasi fusione, rende il codice più pulito e più facile da mantenere e la classe sa come eseguire le proprie attività.

+0

Sì, dato che tutto quello che ho intenzione di fare se il test del tipo true è chiamare un certo metodo di interfaccia su di esso, potrei semplicemente spostare quel metodo di interfaccia nella classe base e fargli fare nulla per impostazione predefinita. Potrebbe essere più elegante della creazione di una funzione virtuale per testare il tipo. – JubJub

+0

Ho fatto un test simile a Binary Worrier dopo i commenti di abatishchev e ho trovato solo 60ms di differenza su 10.000.000 itterazioni. – Ian

+1

Wow, grazie per l'aiuto. Suppongo che mi limiterò a utilizzare gli operatori di controllo dei tipi per ora, a meno che non sembrasse opportuno riorganizzare la struttura della classe. Userò l'operatore 'as' come suggeriva Andrew dal momento che non voglio eseguire il cast ridondante. – JubJub

64

Sono con Ian, probabilmente non si vuole fare questo.

Tuttavia, solo così sai, c'è poca differenza tra i due, oltre 10.000.000 iterazioni

  • Il controllo enum entra a millisecondi (circa)
  • Il è controllare arriva in presso millisecondi (circa)

io personalmente non risolvere questo problema esimo è il modo, ma se fossi costretto a scegliere un metodo sarebbe il built-in controllo IS, la differenza di prestazioni non vale la pena prendere in considerazione l'overhead di codifica.

La mia base e classi derivate

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: Come richiesto ulteriori informazioni sui test.

ho corse entrambi i test da una console app (una build di debug) ogni test è simile al seguente

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

Running in rilascio, ottengo una differenza di 60 - 70 ms, come Ian.

ulteriore aggiornamento - 25 Ottobre 2012
Dopo un paio di anni di distanza ho notato qualcosa su questo, il compilatore può scegliere di omettere bool b = a is MyClassB nel rilascio perché b non viene utilizzato ovunque.

Questo codice. . .

. . . mostra costantemente il check is in entrata a circa 57 millisecondi e il confronto enum in entrata a 29 millisecondi.

NBio preferisco ancora il controllo is, la differenza è troppo piccolo per preoccuparsi

+28

+1 per testare effettivamente le prestazioni, invece di assumere. –

+0

Grazie per il test! Ian ha commentato in precedenza che ha condotto un test delle prestazioni e che la differenza tra i metodi era ancora più bassa di quella che hai trovato. Sono curioso di sapere come sei arrivato ai tuoi numeri. – JubJub

+0

Grazie per l'aggiornamento! Se ho capito bene, il tuo secondo test invalida i risultati precedenti di 700 vs 1000 ms. Forse dovresti aggiornare il post per riflettere questo e incorporare anche la risposta di Andrew per rendere questa la risposta più informativa. – JubJub

20

Ok, quindi stavo chiacchierando di questo con qualcuno e ha deciso di testare questo di più. Per quanto posso dire, le prestazioni di as e is sono entrambe molto buone, rispetto al test del proprio membro o funzione per memorizzare informazioni sul tipo.

Ho utilizzato Stopwatch, che ho appena appreso potrebbe non essere l'approccio più affidabile, così ho anche provato UtcNow. Successivamente, ho anche provato l'approccio temporale Processor che sembra simile a UtcNow compresi tempi di creazione imprevedibili. Ho anche provato a rendere la classe base non astratta senza virtuals ma non sembra avere un effetto significativo.

Ho eseguito questo su un Quad Q6600 con 16 GB di RAM. Anche con iterazioni di 50 miglia, i numeri continuano a rimbalzare intorno ai +/- 50 circa, quindi non leggerò troppo le differenze minori.

E 'stato interessante vedere che x64 creato più veloce, ma eseguito come/è più lento di 86

x64 Modalità di rilascio:
Cronometro:
Come: 561ms
è: proprietà 597ms
Base: campo 539ms
Base: 555ms campo
Base RO: 552ms
GetEnumType virtuale() di prova: 556ms
virtuale ISB() di prova: 588ms
Creare Tempo: 10416ms

utcnow:
Come: 499ms
è: 532ms
proprietà Base: 479ms
campo Base: 502ms campo
Base RO: 491ms
Virtual GetEnumType(): 502ms
Virtual bool IsB(): 522ms
Crea tempo: 285ms (questo numero sembra inaffidabile con h UtcNow. Ho anche 109ms e 806ms)

86 Modalità di rilascio:.
Cronometro:
Come: 391ms
è: proprietà 423ms
Base: 369ms
campo Base: 321ms campo
Base RO : 339ms
Virtual GetEnumType() test: 361 ms
Test virtuale IsB(): 365 ms
Crea tempo: 14106 ms

utcnow:
Come: 348ms
è: 375ms
proprietà Base: 329ms
campo Base: 286ms
Base RO campo: 309ms
GetEnumType virtuale(): 321ms
virtuale bool ISB (): 332ms
Tempo creazione: 544 ms (Questo numero sembra inaffidabile con UtcNow.)

Ecco maggior parte del codice:

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

(Alcuni bonus 5 am-ispirato Shakespeare ...) Essere o non essere: questa è la domanda: Se è più nobile nel codice soffrire Le enumerazioni e proprietà di basi astratte, Oppure di accettare le offerte di un linguista intermedio E invocando le sue istruzioni, fidatevi di loro? Indovinare: meravigliarsi; Non più; e con un tempismo per discernere finiamo il mal di testa e le mille meraviglie subcoscienti che i codificatori con limiti di tempo sono eredi. 'È una chiusura Devotamente da augurare.Morire, no, ma dormire; Sì, dormirò, forse per sognare è e come in quello che può essere derivato dal più basso della classe. –

+0

Possiamo concludere da questo che l'accesso a una proprietà è più veloce su x64 quindi accedere a un campo !!! Perché è davvero una sorpresa per me come può essere? –

+0

Non lo concluderei, perché: "Anche con iterazioni di 50 miglia, i numeri continuano a rimbalzare intorno ai +/- 50 circa, quindi non leggerò troppo le differenze minori." –

13

ho fatto un comparsion prestazioni su due possibilità di confronto di tipo

  1. myObject.GetType() == typeof (MyClass)
  2. myobject è MyClass

Il risultato è: con "è" è di circa 10 volte più veloce !!!

uscita:

Time for Type-confronto: 00: 00: 00,456

tempo per Is-confronto: 00: 00: 00,042

My Code:

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

Sono sempre stato consigliato di evitare di controllare in questo modo, e in Ho un'altra classe. Quindi, piuttosto che fare alcuni controlli e avere azioni diverse a seconda del tipo, fare in modo che la classe sappia come elaborarsi ...

ad es. Obj può essere ISpecialType o IType;

entrambi hanno un metodo DoStuff() definito. Per IType può solo restituire o fare cose personalizzate, mentre ISpecialType può fare altre cose.

Questo rimuove completamente qualsiasi fusione, rende il codice più pulito e più facile da mantenere e la classe sa come eseguire le proprie attività.

3

Point Andrew Hare fatto circa le prestazioni persi quando si esegue is assegno e poi gettato era valido, ma in C# 7.0 che possiamo fare è partita motivo di controllo strega per evitare getto ulteriore seguito:

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

ulteriormente più se è necessario controllare tra più tipi C# 7.0 del modello corrispondenti costrutti permettono ora di fare switch sui tipi:

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

si può leggere di più su pattern matching in C# nella documentazione here .

Problemi correlati