Pytanie w sprawie struct, c#, compiler-construction – C # - Typ wartości Metoda równa - dlaczego kompilator używa refleksji?

15

Po prostu natknąłem się na coś bardzo dziwnego dla mnie: kiedy używasz metody Equals () na typie wartości (a jeśli ta metoda nie została zastąpiona, oczywiście) masz cośbardzo bardzo powolne pola są porównywane jeden do jednego za pomocą odbicia! Jak w :

<code>public struct MyStruct{
   int i;
}

   (...)

   MyStruct s, t;
   s.i = 0;
   t.i = 1;
   if ( s.Equals( t ))   /*  s.i will be compared to t.i via reflection here. */
      (...)
</code>

Moje pytanie: dlaczego kompilator C # nie generuje prostej metody porównywania typów wartości? Coś w stylu (w definicji MyStruct):

<code>   public override bool Equals( Object o ){
      if ( this.i == o.i )
         return true;
      else
         return false;
   }
</code>

Kompilator wie, jakie są pola MyStruct w czasie kompilacji, dlaczego czeka do czasu, gdy środowisko wykonawcze wyliczy pola MyStruct?

Bardzo dziwne dla mnie.

Dzięki :)

DODANY : Przepraszam, po prostu zdaję sobie sprawę, że oczywiścieEquals nie jest słowem kluczowym języka, ale metodą wykonawczą ... Kompilator jest całkowicie nieświadomy tej metody. Dlatego warto tu skorzystać z refleksji.

„Aby użyć standardowej implementacji Equals, twój typ wartości musi być zapakowany i przekazany jako instancja typu referencyjnego System.ValueType. Metoda Equals następnie wykorzystuje odbicie do przeprowadzenia porównania.” - msdn.microsoft.com/en-us/library/ff647790.aspx MrPhil

Twoja odpowiedź

3   odpowiedź
3

Myśląc o efektach, myślę, że zespół projektantów języka zrobił to dobrze. Metody kompilacji znane z C ++ są trudne do zrozumienia dla początkujących. Zobaczmy, co by się stało w C # z autogenerowaną strukturą.

Tak jak teraz, koncepcja .Equals () jest prosta:

Każdy struct dziedziczy Equals z ValueType.Jeśli zostanie zastąpiony, zastosowanie ma niestandardowa metoda Równa.

Jeśli kompilator zawsze tworzy metodę Equals, możemy mieć:

Każdy struct dziedziczy Equals from Object. (ValueType nie będzie już implementować własnej wersji)Object.Equals jest teraz zawsze (!) Nadpisywany przez kompilator wygenerowany metodą Equals lub przez implementację użytkowników

Teraz nasza struktura ma automatycznie nadpisaną metodę nadpisywania, której czytnik kodu nie widzi! Skąd wiesz, że metoda bazowa Object.Equals nie ma zastosowania do twojej struktury? Ucząc się wszystkich przypadków metod generowanych automatycznie przez kompilatory. I to jest dokładnie jedno z obciążeń związanych z nauką C ++.

Uznałby, że dobrą decyzją jest pozostawienie efektywnego struct Equals użytkownikowi i utrzymanie prostych pojęć, wymagających standardowej domyślnej metody Equals.

To powiedziawszy, struktury krytyczne wydajności powinny zastąpić Równe. Poniższy kod pokazuje

3606 vs53 milisekundy zmierzone na .Net 4.5.1

Ten przyrost wydajności jest z pewnością spowodowany unikaniem wirtualnych równości, ale w każdym razie, jeśli wirtualny obiekt. Nazwy byłyby nazywane, zysk byłby znacznie niższy. Przypadki krytyczne dla wydajności nie będą jednak wywoływać Object.Equals, więc zysk tutaj będzie miał zastosowanie.

using System;
using System.Diagnostics;

struct A
{
    public int X;
    public int Y;
}

struct B : IEquatable<B>
{
    public bool Equals(B other)
    {
        return this.X == other.X && this.Y == other.Y;
    }

    public override bool Equals(object obj)
    {
        return obj is B && Equals((B)obj);
    }

    public int X;
    public int Y;
}


class Program
{
    static void Main(string[] args)
    {
        var N = 100000000;

        A a = new A();
        a.X = 73;
        a.Y = 42;
        A aa = new A();
        a.X = 173;
        a.Y = 142;

        var sw = Stopwatch.StartNew();
        for (int i = 0; i < N; i++)
        {
            if (a.Equals(aa))
            {
                Console.WriteLine("never ever");
            }
        }
        sw.Stop();
        Console.WriteLine(sw.ElapsedMilliseconds);

        B b = new B();
        b.X = 73;
        b.Y = 42;
        B bb = new B();
        b.X = 173;
        b.Y = 142;

        sw = Stopwatch.StartNew();
        for (int i = 0; i < N; i++)
        {
            if (b.Equals(bb))
            {
                Console.WriteLine("never ever");
            }
        }
        sw.Stop();
        Console.WriteLine(sw.ElapsedMilliseconds);
    }
}

Zobacz teżhttp://blog.martindoms.com/2011/01/03/c-tip-override-equals-on-value-types-for-better-performance/

Nitpick: TheReferenceEquals(null, obj) wezwanie jest technicznie zbędneis wyrażenie ma wartość false, jeśli podane wyrażenie (obj) ma wartość NULL. Jestem jednak pewien, że nie wpływa to w żaden sposób na wyniki testu porównawczego. Mimo to nie polegałbym na kompilatorze, aby go zoptymalizować, jeśli miałoby to znaczenie. tne
Warto zauważyć, że kompilator nie używa Reflection; po prostu używa metody wirtualnej do metodyValueType.Equals; ponieważ ta metoda oczekujethis być typem klasy [ValueType jest, pomimo swojej nazwy, klasy] wartość musi być zapakowana. Pojęciowo mogłoby być miło, gdybyValueType zdefiniował metodę statycznąValueTypeEquals<T>(ref T it, Object other) { ValueTypeComparer<T>.Compare(ref it, other); i zalecił, aby w miarę możliwości kompilatory preferowały to, co wirtualneEquals metoda. Takie podejście mogłoby mieć ... supercat
@tne: tak dobrze! usunięto to. citykid
... umożliwiło środowisku wykonawczemu wygenerowanie porównywarki dla każdego typu tylko przy pierwszym użyciu, a następnie dostęp do tego porównywarki bezpośrednio w kolejnych wywołaniach. supercat
10

b:

public override bool Equals(object obj)
{
    if (obj == null)
    {
        return false;
    }
    RuntimeType type = (RuntimeType) base.GetType();
    RuntimeType type2 = (RuntimeType) obj.GetType();
    if (type2 != type)
    {
        return false;
    }
    object a = this;
    if (CanCompareBits(this))
    {
        return FastEqualsCheck(a, obj);
    }
    FieldInfo[] fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
    for (int i = 0; i < fields.Length; i++)
    {
        object obj3 = ((RtFieldInfo) fields[i]).InternalGetValue(a, false);
        object obj4 = ((RtFieldInfo) fields[i]).InternalGetValue(obj, false);
        if (obj3 == null)
        {
            if (obj4 != null)
            {
                return false;
            }
        }
        else if (!obj3.Equals(obj4))
        {
            return false;
        }
    }
    return true;
}

Gdy będzie to możliwe, zostanie przeprowadzone porównanie bitowe (zwróć uwagę na CanCompareBits i FastEqualsCheck, oba zdefiniowane jako InternalCall. JIT przypuszczalnie wstrzyknąłby odpowiedni kod tutaj. Co do tego, dlaczego jest tak wolny, nie mogłem ci powiedzieć .

Sprawdź to. Masz rację. :) Sylvain Rodrigue
Zastanawiam się, czy nie byłoby żadnych problemów ze zgodnością, jeśli środowisko wykonawcze miało automatycznie generowaćEquals przesłonięcie dla każdej struktury, która jeszcze nie zdefiniowała:bool Equals(object other) { return StructComparer<thisType>.EqualsProc(ref this, other); }, gdzieEqualsProc było statycznym polem delegata w klasie statycznejStructComparer<thisType>? Takie podejście pozwoliłoby uniknąć konieczności używania odbicia za każdym razem, gdy obiekt byłby porównywany, a także mogłoby uniknąć kroku boksu. supercat
10

kiedy nie musi. Po prostu porównuje wartości po kolei w przypadkustruct jeśli może to zrobić. Jednakże, jeśli którykolwiek zstruct członkowie (lub członkowie członków, potomkowie) zastępująobject.Equals i zapewnia własną implementację, oczywiście nie może polegać na porównaniu bit po bicie do obliczenia wartości zwracanej.

Powód jest powolny, ponieważ parametr doEquals jest typuobject i typy wartości muszą być umieszczone w ramce, aby mogły być traktowane jakoobject. Boksowanie polega na przydzielaniu pamięci na stercie i kopiowaniu pamięci do tego typu wartości.

Możesz ręcznie przeładowaćEquals metoda, która bierze twój własnystruct jako parametr zapobiegający boksowaniu:

public bool Equals(MyStruct obj) {
     return obj.i == i;
}
Sylvain: Oni mają rację. Jak powiedział Jon, jeśli struktura zawiera typy referencyjne jako elementy, musi wywoływać Równe na tych polach. Zaktualizowałem odpowiedź, aby to odzwierciedlić. Chodziło mi o to, że nie korzysta z odbicia, gdy tego nie potrzebuje (jak na przykład). Mehrdad Afshari
Kiedy to pisałem, czytałem i stwierdziłem, że niektórzy autorzy frameworków .Net (Cwalina, Abrams) potwierdzają, że Equals używa refleksji nad typami wartości. Ale może po prostu w Framework 2.0? Sylvain Rodrigue
Muszę powiedzieć, że nie rozumiem, dlaczego nie można dokonać mądrego porównania, gdy istnieją odniesienia. Jeśli dwa odniesienia wskazują na ten sam obiekt, kiedy wskaźniki nie byłyby dokładnie równe? snarf
„Aby użyć standardowej implementacji Equals, twój typ wartości musi być zapakowany i przekazany jako instancja typu referencyjnego System.ValueType. Metoda Equals następnie wykorzystuje odbicie do przeprowadzenia porównania.” -msdn.microsoft.com/en-us/library/ff647790.aspx MrPhil

Powiązane pytania