Dlaczego C # nie może porównać ze sobą dwóch typów obiektów, ale VB nie?


152

Mam dwa obiekty w C # i nie wiem, czy jest to typ Boolean, czy inny. Jednak gdy próbuję porównać te C # nie daje poprawnej odpowiedzi. Wypróbowałem ten sam kod z VB.NET i to się udało!

Czy ktoś może mi powiedzieć, jak to naprawić, jeśli istnieje rozwiązanie?

DO#:

object a = true;
object b = true;
object c = false;
if (a == b) c = true;
MessageBox.Show(c.ToString()); //Outputs False !!

VB.NET:

Dim a As Object = True
Dim b As Object = True
Dim c As Object = False
If (a = b) Then c = True
MessageBox.Show(c.ToString()) '// Outputs True

3
co, jeśli zmienisz porównywanie równości na a.Equals(b)?
Jason Meckley,

8
To dobre pytanie ze względów pedagogicznych.
Lobo,

10
Ponieważ Twój kod VB.NET nie jest równy kodowi C #.
Security Hound,

9
Po przypisaniu do asiebie otrzymujesz boks i utworzysz pudełko zawierające true. Kiedy przypisujesz do bsiebie, otrzymasz kolejne pudełko zawierające również true. Podczas porównywania ai b, ponieważ oba są typu czasu kompilacji object, wywołujesz przeciążenie operator ==(object, object)zdefiniowane przez specyfikację języka C #. To przeciążenie sprawdza, czy odwołania przechodzą do tego samego obiektu. Ponieważ masz dwa pola, wynikiem jest false, a stwierdzenie „pod” ifnie będzie działać. Aby lepiej to zrozumieć, spróbuj zmienić przypisanie bdo tego: object b = a;Teraz masz tylko jedno pudełko.
Jeppe Stig Nielsen

3
Wcześniej miałem okazję powiedzieć „Uważaj, zakładając, że VB.NET i C # to ten sam język, którym mówi się z innym akcentem - nie są”
AakashM

Odpowiedzi:


168

W języku C # ==operator (w przypadku zastosowania do wyrażeń typu referencyjnego) wykonuje sprawdzenie równości odwołań, chyba że jest przeciążony . Porównujesz dwa odniesienia, które są wynikiem konwersji bokserskich, więc są to różne odniesienia.

EDYCJA: W przypadku typów, które przeciążają ==, możesz uzyskać inne zachowanie - ale jest to oparte na typie wyrażeń w czasie kompilacji . Na przykład stringzapewnia ==(string, string):

string x = new string("foo".ToCharArray());
string y = new string("foo".ToCharArray());
Console.WriteLine(x == y); // True
Console.WriteLine((object) x == (object) y); // False

Tutaj pierwsze porównanie używa przeciążonego operatora, ale drugie używa „domyślnego” porównania referencji.

W VB =operator wykonuje o wiele więcej pracy - nie jest to nawet równoważne użyciu object.Equals(x, y), ponieważ takie rzeczy Option Comparemogą wpływać na sposób porównywania tekstu.

Zasadniczo operatory nie działają w ten sam sposób i nie mają działać w ten sam sposób.


17
+1 Wiedziałem, że będziesz w pobliżu, KOCHASZ tego typu tajemnicze pytania :)
Abdusalam Ben Haj

3
@AbZy: Miałem nadzieję, że będę w stanie przedstawić bardziej szczegółowe wyjaśnienie tego, co =zrobiłem w VB, ale specyfikacja nie jest zbyt jasna.
Jon Skeet,

ciekawa rzecz, ale zmiana obiektu na dynamiczny zachowuje się tak samo jak VB
VladL,

4
@VladL: Tak, ponieważ wtedy przejdzie przez typy czasu wykonania i przeprowadzi bool == boolporównanie.
Jon Skeet,

1
@Mahdi Lobo mógł dostarczyć kod, ale jego odpowiedź jest również błędna, w przeciwieństwie do Jona.
Servy

79

Oprócz odpowiedzi Jona, która wyjaśnia stronę C #, oto co robi VB:

W VB z Option Strict On, porównanie poprzez = zawsze sprawdza równość wartości, a nigdy równość odniesienia. W rzeczywistości Twój kod nawet się nie kompiluje po przełączeniu, Option Strict Onponieważ System.Objectnie definiuje pliku Operator=. Powinieneś zawsze mieć tę opcję włączoną, łapie ona owady skuteczniej niż muchołówka (chociaż w twoim konkretnym przypadku to luźne zachowanie działa właściwie). 1

W rzeczywistości, z Option Strict OnVB zachowuje się jeszcze bardziej rygorystyczne niż C # w C #, a == b albo wyzwala wywołanie SomeType.operator==(a, b)lub, jeśli nie istnieje, powołuje porównania równości odniesienia (co jest równoznaczne z wywołaniem object.ReferenceEquals(a, b)).

Z drugiej strony w języku VB porównanie a = b zawsze wywołuje operator równości. 2 Jeśli chcesz użyć porównania równości odniesień, musisz użyć a Is b(co znowu jest tym samym co Object.ReferenceEquals(a, b)).


1) Oto dobra wskazówka, dlaczego używanie Option Strict Offjest złym pomysłem: korzystałem z VB.NET przez prawie dekadę, od oficjalnego wydania .NET aż do kilku lat temu i nie mam pojęcia, co a = bsię z nim dzieje Option Strict Off. Robi jakieś porównanie równości, ale co dokładnie się dzieje i dlaczego, nie mam pojęcia. Jest to jednak bardziej złożone niż dynamicfunkcja C # (ponieważ opiera się na dobrze udokumentowanym interfejsie API). Oto, co mówi MSDN:

Ponieważ Option Strict Onzapewnia silne pisanie , zapobiega niezamierzonym konwersjom typów z utratą danych, uniemożliwia późne wiązanie i poprawia wydajność, zdecydowanie zaleca się jego użycie.

2) Jon wspomniał o jednym wyjątku, łańcuchach, gdzie porównanie równości robi więcej rzeczy ze względu na kompatybilność wsteczną.


4
+1. Myślę, że jest to jeden przypadek, w którym projektantom VB.NET udało się uczynić język „po prostu działającym” dla programistów wywodzących się z VB6 i VBA, gdzie OOP jest znacznie mniej widoczne, a więc pojęcie równości odniesień jest znacznie mniej ważne. Koder VB może napisać dobry działający kod, nie myśląc zbyt wiele o obiektach i tak dalej.
John M Gant,

5
+1 Nie jest to głosowane tak bardzo, jak powinno. Nie używanie Option Strict Onmusi być uznane za przestępstwo ...
Deer Hunter,

1
@JohnMGant: Koder, który nie rozumie znaczenia tożsamości referencyjnej, może być w stanie napisać kod, który działa, ale jest mało prawdopodobne, aby naprawdę wiedział, jakie rzeczy można bezpiecznie zmienić, jakie zmiany zawsze zepsują, a jakie zmiany mogą wydają się działać, ale powodują niepożądane, nieprzyjemne skutki uboczne (np. powodując, że to, co powinno być odwołaniami do różnych zmiennych obiektów, które mają ten sam stan, zamiast tego będą odwołaniami do tego samego obiektu). Jeśli obiekty są rzadko mutowane, taka zmiana może nie powodować żadnych bezpośrednich problemów, ale może spowodować, że trudne do znalezienia błędy pojawią się później.
supercat

4

Instancje obiektów nie są porównywane z operatorem „==”. Powinieneś użyć metody „równa się”. Za pomocą operatora „==” porównujemy referencje, a nie obiekty.

Spróbuj tego:

public class MyObject
{
    public MyObject(String v)
    {
        Value = v;
    }
    public String Value { get; set; }
}

MyObject a = new MyObject("a");
MyObject b = new MyObject("a");
if(a==b){
    Debug.WriteLine("a reference is equal to b reference");
}else{
    Debug.WriteLine("a reference is not equal to b reference");
}
if (a.Equals(b)) {
    Debug.WriteLine("a object is equal to b object");
} else {
    Debug.WriteLine("a object is not equal to b object");
}

Wyniki:

a reference is not equal to b reference
a object is not equal to b object

Teraz spróbuj tego:

public class MyObject
{
    public MyObject(String v)
    {
        Value = v;
    }
    public String Value { get; set; }

    public bool Equals(MyObject o)
    {
        return (Value.CompareTo(o.Value)==0);
    }
}
MyObject a = new MyObject("a");
MyObject b = new MyObject("a");
if(a==b){
    Debug.WriteLine("a reference is equal to b reference");
}else{
    Debug.WriteLine("a reference is not equal to b reference");
}
if (a.Equals(b)) {
    Debug.WriteLine("a object is equal to b object");
} else {
    Debug.WriteLine("a object is not equal to b object");
}

Wyniki:

a reference is not equal to b reference
a object is equal to b object

1
Dzieje się tak po prostu dlatego, że nie zastąpiłeś operator ==. Jeśli przesłonisz ten operator i nie jest równy, to wynik zostanie odwrócony. Nie ma nic nieodłącznego w porównywaniu odniesienia operator ==ani nic nieodłącznego w porównywaniu wartości w Equals. To tylko dwa sposoby określania równości; oba mają domyślne implementacje porównania referencji i można je przesłonić, aby zrobiły to, co chcesz. Jedyną różnicą jest to, że Equalsjest wirtualny, a operator ==nie.
Servy

1
@Servy: Zauważ, że nie możesz zastąpić == - możesz go tylko przeciążać .
Jon Skeet,

1
Przepraszam, -1. Ta odpowiedź jest po prostu niepoprawna i nie powinna być akceptowana.
Konrad Rudolph

Gdzieś na tę odpowiedź czeka Java pytanie.
Chad Schouggins

3

Problem polega na tym, że operator == w C # jest wywołaniem metody statycznej (no może nie technicznie, ale może być tak pomyślana) na podstawie typu czasu kompilacji dwóch parametrów. Nie ma znaczenia, jakie są rzeczywiste typy środowiska uruchomieniowego tych obiektów.

Na podstawie tego typu czasu kompilacji kompilator określi, której implementacji operator ==użyć. Może używać domyślnej objectimplementacji, może używać jednego z przeciążeń numerycznych dostarczonych przez język lub może to być implementacja zdefiniowana przez użytkownika.

Różni się to od VB tym, że VB nie określa implementacji w czasie kompilacji. Czeka do czasu wykonania i sprawdza dwa parametry, które otrzymuje, aby określić, której implementacji ==operatora powinien użyć.

Twój kod zawiera wartości logiczne, ale są one w zmiennych typu object. Ponieważ zmienna jest typu object, kompilator C # używa objectimplementacji ==, która porównuje odwołania , a nie wystąpienia obiektów. Ponieważ wartości logiczne to pola, nie mają tego samego odniesienia, mimo że ich wartości są takie same.

Kod VB nie dba o typ zmiennej. Czeka do czasu uruchomienia, a następnie sprawdza dwie zmienne, widzi, że obie są w rzeczywistości typu boolowskiego, więc używa ==implementacji operatora boolowskiego . Ta implementacja porównuje wartości parametrów logicznych, a nie ich referencji (a wartości logiczne zostaną rozpakowane przed wywołaniem tego operatora, więc porównanie referencji nie ma już nawet sensu). Ponieważ wartości logiczne są takie same, zwraca prawdę.


To wygląda dobrze dla C #; Nie wiem wystarczająco dużo o tym, co dokładnie =robi w VB, żeby na pewno powiedzieć.
Jon Skeet

@JonSkeet Fair wystarczająco.
Servy

Za msdn.microsoft.com/en-us/library/cey92b0t(v=vs.110).aspx , w dziale „Typeless Programowanie z relacyjnych operatorów porównania”: =razem ze wszystkimi innymi relacyjnych operatorów porównania, takich jak <, >=itp , są traktowane w specjalny sposób, gdy obie strony lub obie strony są traktowane Object. To specjalne traktowanie jest wykonywane po to, aby programiści VB6, którzy są przyzwyczajeni do używania typu znanego jako Variantpre-.NET VB, mogą korzystać z ObjectVB.Net w sposób, w jaki używali go Variantwcześniej.
rskar

Ujmując to inaczej, pomijając skutki przeciążenia i Option Strict On, VB =jest nastawiony na rozpakowywanie an, Objectdopóki nie dotrze do łańcucha lub wartości liczbowej.
rskar
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.