Czy użycie „var” wpłynie na wydajność?


230

Wcześniej zadałem pytanie, dlaczego widzę tak wiele przykładów używających varsłowa kluczowego i uzyskałem odpowiedź, że chociaż jest to konieczne tylko w przypadku typów anonimowych, to jednak jest ono używane, aby pisanie kodu było „szybsze” / łatwiejsze i „tylko dlatego”.

Podążając za tym linkiem („C # 3.0 - Var Is Not Objec”) zobaczyłem, że varkompiluje się do właściwego typu w IL (zobaczycie go w połowie artykułu).

Moje pytanie brzmi: o ile więcej, jeśli w ogóle, kod IL wymaga użycia varsłowa kluczowego, a czy byłby nawet bliski mierzalnego poziomu wydajności kodu, gdyby był używany wszędzie?


1
pytanie, na które odpowiedziano przed wiekami, po prostu chciałem dodać jeszcze jedną rzecz przeciwko var - mimo że został rozwiązany w czasie kompilacji, nie został poprawnie wykryty przez „Znajdź wszystkie referencje” Visual Studio i „Znajdź użycie” Resharpera, jeśli chcesz znaleźć wszystkie zastosowania tego typu - i to nie zostanie naprawione, ponieważ byłoby zbyt wolne.
KolA

Zmienne @KolA zadeklarowane z varcałą pewnością współpracują z „Znajdź wszystkie referencje” w Visual Studio 2019, więc jeśli kiedykolwiek był uszkodzony, został naprawiony. Ale mogę potwierdzić, że działa już w Visual Studio 2012, więc nie jestem pewien, dlaczego twierdziłeś, że to nie działa.
Herohtar

@Herohtar spróbuj zastosować kod „klasa X {} X GetX () {zwróć nowy X ();} void UseX () {var x = GetX ();}” i znajdź wszystkie odniesienia do X, „var x = GetX ( ) ”bit nie jest podświetlony - to właśnie miałem na myśli najnowszy VS2019. Jest on podświetlony, jeśli użyjesz „X x = GetX ()” zamiast var
KolA

1
@KolA Ach, rozumiem, co masz na myśli - varnie będzie traktowany jako odniesienie, Xgdy użyjesz opcji „Znajdź wszystkie odniesienia” X. Co ciekawe, jeśli używasz „Znajdź wszystkie odniesienia” na varw tym piśmie, że będzie pokazać odniesień do X(choć nadal nie będzie lista varinstrukcji). Dodatkowo, gdy kursor jest włączony var, podświetli wszystkie wystąpienia Xtego samego dokumentu (i odwrotnie).
Herohtar,

Odpowiedzi:


316

Nie ma dodatkowego kodu IL dla varsłowa kluczowego: wynikowa IL powinna być identyczna dla typów nieanonimowych. Jeśli kompilator nie może utworzyć tej IL, ponieważ nie może dowiedzieć się, jakiego typu zamierzasz użyć, pojawi się błąd kompilatora.

Jedyną sztuczką jest varustalenie dokładnego typu, w którym mógłbyś wybrać typ interfejsu lub typ nadrzędny, jeśli ustawiłeś typ ręcznie.


Zaktualizuj 8 lat później

Muszę to zaktualizować, ponieważ zmieniło się moje rozumienie. Teraz uważam, że może mieć to varwpływ na wydajność w sytuacji, gdy metoda zwraca interfejs, ale użyłbyś dokładnego typu. Na przykład, jeśli masz tę metodę:

IList<int> Foo()
{
    return Enumerable.Range(0,10).ToList();
}

Rozważ te trzy linie kodu, aby wywołać metodę:

List<int> bar1 = Foo();
IList<int> bar = Foo();
var bar3 = Foo();

Wszystkie trzy kompilują się i wykonują zgodnie z oczekiwaniami. Jednak pierwsze dwie linie nie są dokładnie takie same, a trzecia linia będzie pasować do drugiej, a nie pierwszej. Ponieważ podpis Foo()ma zwrócić an IList<int>, w ten sposób kompilator zbuduje bar3zmienną.

Z punktu widzenia wydajności przeważnie nie zauważysz. Są jednak sytuacje, w których wydajność trzeciej linii może nie być tak szybka jak wydajność pierwszej . W miarę dalszego używania bar3zmiennej kompilator może nie być w stanie wywołać wywołań metod w ten sam sposób.

Pamiętaj, że możliwe (prawdopodobnie nawet) drgania będą w stanie usunąć tę różnicę, ale nie jest to gwarantowane. Ogólnie rzecz biorąc, powinieneś nadal uważać varsię za czynnik bez znaczenia pod względem wydajności. Z pewnością wcale nie jest tak, jak przy użyciu dynamiczmiennej. Ale stwierdzenie, że to nigdy nie robi różnicy, może być przesadzeniem.


23
IL powinna być nie tylko identyczna - jest identyczna. var i = 42; kompiluje do dokładnie tego samego kodu co int i = 42;
Brian Rasmussen,

15
@BrianRasmussen: Wiem, że twój post jest stary jest stary, ale zakładam var i = 42;(infers type is int) NIE jest identyczny z long i = 42;. Tak więc w niektórych przypadkach możesz przyjmować błędne założenia dotyczące wnioskowania o typie. Może to powodować nieuchwytne błędy czasu wykonania przypadku / krawędzi, jeśli wartość nie będzie pasować. Z tego powodu dobrym pomysłem może być jawne określenie, gdy wartość nie ma jawnego typu. Na przykład var x = new List<List<Dictionary<int, string>()>()>()byłby do przyjęcia, ale var x = 42jest nieco niejednoznaczny i powinien być zapisany jako int x = 42. Ale każdemu z nich ...
Nelson Rothermel

50
@NelsonRothermel: var x = 42; nie jest dwuznaczny. Literały całkowite są tego typu int. Jeśli chcesz dosłownie długo piszesz var x = 42L;.
Brian Rasmussen

6
Uhm, co oznacza IL w C #? Tak naprawdę nigdy o tym nie słyszałem.
puretppc

15
W twoim przykładzie 3 linii kodu, które zachowują się inaczej, pierwsza linia nie jest kompilowana . Druga i trzecia linia, które oba się kompilują, robią dokładnie to samo. Gdyby Foozwrócił a List, a nie an IList, wówczas wszystkie trzy linie by się skompilowały, ale trzecia linia zachowałaby się jak pierwsza linia , a nie druga.
Servy

72

Jak mówi Joel, kompilator sprawdza w czasie kompilacji, jaki typ zmiennej powinien być, w rzeczywistości jest to tylko sztuczka, którą kompilator wykonuje, aby zapisać naciśnięcia klawiszy, więc na przykład

var s = "hi";

zostaje zastąpiony przez

string s = "hi";

przez kompilator przed wygenerowaniem jakiejkolwiek IL. Wygenerowana IL będzie dokładnie taka sama, jakbyś wpisał ciąg.


26

Jak nikt jeszcze nie wspomniał o odbłyśniku ...

Jeśli skompilujesz następujący kod C #:

static void Main(string[] args)
{
    var x = "hello";
    string y = "hello again!";
    Console.WriteLine(x);
    Console.WriteLine(y);
}

Następnie użyj na nim reflektora, otrzymasz:

// Methods
private static void Main(string[] args)
{
    string x = "hello";
    string y = "hello again!";
    Console.WriteLine(x);
    Console.WriteLine(y);
}

Zatem odpowiedź wyraźnie nie ma wpływu na wydajność środowiska uruchomieniowego!


17

Dla następującej metody:

   private static void StringVsVarILOutput()
    {
        var string1 = new String(new char[9]);

        string string2 = new String(new char[9]);
    }

Wyjście IL jest następujące:

        {
          .method private hidebysig static void  StringVsVarILOutput() cil managed
          // Code size       28 (0x1c)
          .maxstack  2
          .locals init ([0] string string1,
                   [1] string string2)
          IL_0000:  nop
          IL_0001:  ldc.i4.s   9
          IL_0003:  newarr     [mscorlib]System.Char
          IL_0008:  newobj     instance void [mscorlib]System.String::.ctor(char[])
          IL_000d:  stloc.0
          IL_000e:  ldc.i4.s   9
          IL_0010:  newarr     [mscorlib]System.Char
          IL_0015:  newobj     instance void [mscorlib]System.String::.ctor(char[])
          IL_001a:  stloc.1
          IL_001b:  ret
        } // end of method Program::StringVsVarILOutput

14

Kompilator C # określa prawdziwy typ varzmiennej w czasie kompilacji. Nie ma różnicy w wygenerowanej IL.


14

Tak więc, dla jasności, jest to leniwy styl kodowania. Wolę typy rodzime, biorąc pod uwagę wybór; Wezmę ten dodatkowy „szum”, aby upewnić się, że piszę i czytam dokładnie to, co myślę, że jestem w czasie kodowania / debugowania. * wzrusza ramionami *


1
To tylko twój subiektywny pogląd, a nie odpowiedź na pytanie o wydajność. Prawidłowa odpowiedź jest taka, że ​​nie ma to wpływu na wydajność. Głosowałem za zamknięciem
Anders

To nie odpowiada na pytanie, czy varw ogóle wpływa na wydajność; po prostu wyrażasz swoją opinię, czy ludzie powinni go używać.
Herohtar

Wnioskowanie typu na podstawie wartości później, na przykład przejście z int 5 na float 5.25, może absolutnie powodować problemy z wydajnością. * wzrusza ramionami
ChrisH

Nie, nie spowoduje to żadnych problemów z wydajnością; pojawią się błędy kompilacji w każdym miejscu, w którym oczekiwana jest zmienna typu, intponieważ nie może ona automatycznie przekonwertować float, ale to dokładnie to samo, co by się stało, gdybyś wyraźnie użył, inta następnie zmienił na float. W każdym razie twoja odpowiedź wciąż nie odpowiada na pytanie „czy używanie varwpływa na wydajność?” (szczególnie jeśli chodzi o wygenerowaną IL)
Herohtar,

8

Nie sądzę, żebyś właściwie zrozumiał, co czytasz. Jeśli zostanie skompilowany do odpowiedniego typu, to nie ma żadnej różnicy. Kiedy to zrobię:

var i = 42;

Kompilator wie, że to int i generuje kod tak, jakbym napisał

int i = 42;

Jak mówi post, do którego prowadzi link, kompiluje się do tego samego typu. To nie jest kontrola czasu wykonywania ani nic innego wymagającego dodatkowego kodu. Kompilator po prostu domyśla się, jaki musi być typ, i używa tego.


Tak, ale co jeśli później i = i - someVar i someVar = 3.3. Teraz jestem Int. Lepiej jest wyrazić się nie tylko, aby dać kompilatorowi szybki start w znajdowaniu błędów, ale także w celu zminimalizowania błędów czasu wykonywania lub konwersji typu spowalniających proces. * wzruszenie ramion * To sprawia, że ​​kod jest lepszy do samoopisywania. Robiłem to przez długi, długi czas. Za każdym razem wezmę „zaszumiony” kod z wyraźnymi typami, o ile zostanie wybrany.
ChrisH

5

Korzystanie z var nie wiąże się z żadnymi kosztami wydajności. Podejrzewam jednak, że wystąpiłby koszt wydajności kompilacji, ponieważ kompilator musi wnioskować o typie, chociaż najprawdopodobniej będzie to nieistotne.


10
RHS i tak musi mieć obliczony typ - kompilator wyłapałby niedopasowane typy i rzucił błąd, więc chyba nie jest to koszt.
Jimmy,

3

Jeśli kompilator może dokonywać automatycznego wnioskowania na temat typu, nie będzie żadnego problemu z wydajnością. Oba wygenerują ten sam kod

var    x = new ClassA();
ClassA x = new ClassA();

jeśli jednak konstruujesz typ dynamicznie (LINQ ...), to varjest twoje jedyne pytanie i istnieje inny mechanizm do porównania, aby powiedzieć, jaka jest kara.


3

Zawsze używam słowa var w artykułach internetowych lub pismach przewodników.

Szerokość edytora tekstowego artykułu online jest niewielka.

Jeśli to napiszę:

SomeCoolNameSpace.SomeCoolClassName.SomeCoolSubClassName coolClass = new SomeCoolNameSpace.SomeCoolClassName.SomeCoolSubClassName();

Zobaczysz, że powyższy renderowany tekst przed kodem jest zbyt długi i wypływa z pudełka, zostaje ukryty. Czytnik musi przewinąć w prawo, aby zobaczyć pełną składnię.

Dlatego zawsze używam słowa kluczowego var w pismach artykułów internetowych.

var coolClass = new SomeCoolNameSpace.SomeCoolClassName.SomeCoolSubClassName();

Cały renderowany kod wstępny po prostu mieści się na ekranie.

W praktyce do deklarowania obiektu rzadko używam var, polegam na inteligencji, aby szybciej deklarować obiekt.

Przykład:

SomeCoolNamespace.SomeCoolObject coolObject = new SomeCoolNamespace.SomeCoolObject();

Ale do zwracania obiektu z metody używam var do szybszego pisania kodu.

Przykład:

var coolObject = GetCoolObject(param1, param2);

Jeśli piszesz dla studentów, jedz własne psie pożywienie i zawsze pisz je w ten sam „poprawny” sposób. Uczniowie często biorą rzeczy w 100% dosłownie i do serca, i zaczynają stosować wszelkie niechlujne nawyki, które nabiorą po drodze. 0,02 USD
ChrisH,

1

„var” to jedna z tych rzeczy, które ludzie lubią lub nienawidzą (jak regiony). Chociaż, w przeciwieństwie do regionów, var jest absolutnie konieczne przy tworzeniu anonimowych klas.

Dla mnie var ma sens, gdy tworzysz nowy obiekt bezpośrednio jak:

var dict = new Dictionary<string, string>();

Biorąc to pod uwagę, możesz łatwo zrobić:

Dictionary<string, string> dict = nowy i intellisense wypełnią resztę tutaj.

Jeśli chcesz pracować tylko z określonym interfejsem, nie możesz używać var, chyba że wywoływana metoda bezpośrednio zwróci interfejs.

Resharper wydaje się być po stronie używania „var”, co może zmusić więcej ludzi do zrobienia tego w ten sposób. Ale zgadzam się, że trudniej jest przeczytać, jeśli wywołujesz metodę i nie jest oczywiste, co zwraca nazwa.

var sam nie spowalnia, ale jest jedno zastrzeżenie, o którym nie myśli wiele osób. Jeśli to zrobisz, var result = SomeMethod();kod po tym spodziewa się jakiegoś wyniku z powrotem w miejscu, w którym wywołasz różne metody, właściwości lub cokolwiek innego. Jeśli SomeMethod()zmienił definicję na inny typ, ale nadal spełniał warunki, których oczekiwał inny kod, po prostu stworzyłeś naprawdę paskudny błąd (oczywiście jeśli nie ma testów jednostkowych / integracyjnych).


0

Zależy to od sytuacji, jeśli spróbujesz użyć tego kodu, poniżej.

Wyrażenie jest konwertowane na „OBIEKT” i znacznie obniża wydajność, ale jest to odizolowany problem.

KOD:

public class Fruta
{
    dynamic _instance;

    public Fruta(dynamic obj)
    {
        _instance = obj;
    }

    public dynamic GetInstance()
    {
        return _instance;
    }
}

public class Manga
{
    public int MyProperty { get; set; }
    public int MyProperty1 { get; set; }
    public int MyProperty2 { get; set; }
    public int MyProperty3 { get; set; }
}

public class Pera
{
    public int MyProperty { get; set; }
    public int MyProperty1 { get; set; }
    public int MyProperty2 { get; set; }
}

public class Executa
{
    public string Exec(int count, int value)
    {
        int x = 0;
        Random random = new Random();
        Stopwatch time = new Stopwatch();
        time.Start();

        while (x < count)
        {
            if (value == 0)
            {
                var obj = new Pera();
            }
            else if (value == 1)
            {
                Pera obj = new Pera();
            }
            else if (value == 2)
            {
                var obj = new Banana();
            }
            else if (value == 3)
            {
                var obj = (0 == random.Next(0, 1) ? new Fruta(new Manga()).GetInstance() : new Fruta(new Pera()).GetInstance());
            }
            else
            {
                Banana obj = new Banana();
            }

            x++;
        }

        time.Stop();
        return time.Elapsed.ToString();
    }

    public void ExecManga()
    {
        var obj = new Fruta(new Manga()).GetInstance();
        Manga obj2 = obj;
    }

    public void ExecPera()
    {
        var obj = new Fruta(new Pera()).GetInstance();
        Pera obj2 = obj;
    }
}

Powyższe wyniki z ILSPY.

public string Exec(int count, int value)
{
    int x = 0;
    Random random = new Random();
    Stopwatch time = new Stopwatch();
    time.Start();

    for (; x < count; x++)
    {
        switch (value)
        {
            case 0:
                {
                    Pera obj5 = new Pera();
                    break;
                }
            case 1:
                {
                    Pera obj4 = new Pera();
                    break;
                }
            case 2:
                {
                    Banana obj3 = default(Banana);
                    break;
                }
            case 3:
                {
                    object obj2 = (random.Next(0, 1) == 0) ? new Fruta(new Manga()).GetInstance() : new Fruta(new Pera()).GetInstance();
                    break;
                }
            default:
                {
                    Banana obj = default(Banana);
                    break;
                }
        }
    }
time.Stop();
return time.Elapsed.ToString();
}

Jeśli chcesz wykonać ten kod, użyj kodu poniżej i uzyskaj różnicę razy.

        static void Main(string[] args)
    {
        Executa exec = new Executa();            
        int x = 0;
        int times = 4;
        int count = 100000000;
        int[] intanceType = new int[4] { 0, 1, 2, 3 };

        while(x < times)
        {                
            Parallel.For(0, intanceType.Length, (i) => {
                Console.WriteLine($"Tentativa:{x} Tipo de Instancia: {intanceType[i]} Tempo Execução: {exec.Exec(count, intanceType[i])}");
            });
            x++;
        }

        Console.ReadLine();
    }

pozdrowienia

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.