Mam ogólną metodę zdefiniowaną w ten sposób:
public void MyMethod<T>(T myArgument)
Pierwszą rzeczą, którą chcę zrobić, jest sprawdzenie, czy wartość myArgument jest wartością domyślną dla tego typu, mniej więcej tak:
if (myArgument == default(T))
Ale to się nie kompiluje, ponieważ nie mam gwarancji, że T zaimplementuje operator ==. Więc zmieniłem kod na ten:
if (myArgument.Equals(default(T)))
Teraz to się kompiluje, ale zakończy się niepowodzeniem, jeśli myArgument ma wartość null, co jest częścią tego, co testuję. Mogę dodać jawny czek zerowy w następujący sposób:
if (myArgument == null || myArgument.Equals(default(T)))
Teraz wydaje mi się to zbędne. ReSharper sugeruje nawet, że zmienię część myArgument == null na myArgument == default (T), od której zacząłem. Czy istnieje lepszy sposób na rozwiązanie tego problemu?
Muszę obsługiwać zarówno typy referencji, jak i typy wartości.
if (myArgument?.Equals( default(T) ) != null )
.true
w każdym przypadku, ponieważEquals
zawsze będzie wywoływany dla typów wartości, ponieważmyArgument
nie może byćnull
w tym przypadku, a wynikEquals
(boolean) nigdy nie będzienull
.Odpowiedzi:
Aby uniknąć boksu, najlepszym sposobem na porównanie generyków dla równości jest
EqualityComparer<T>.Default
. Dotyczy toIEquatable<T>
(bez boksu), a takżeobject.Equals
obsługuje wszystkieNullable<T>
„podniesione” niuanse. W związku z tym:To będzie pasować:
Nullable<T>
źródło
Person
,p1.Equals(p2)
będzie zależeć od tego, czy implementujeIEquatable<Person>
na API publicznej lub poprzez wyraźne realizacji - czyli może kompilator zobaczyć publicznąEquals(Person other)
metodę. Jednak; w rodzajowych , to samo stosuje się do IL wszystkieT
;T1
że dzieje się wdrożenieIEquatable<T1>
musi być traktowane identycznieT2
że nie - więc nie, to nie będzie rozpoznaćEquals(T1 other)
metody, nawet jeśli istnieje w czasie wykonywania. W obu przypadkach należy równieżnull
pomyśleć o (dowolnym obiekcie). Tak więc w przypadku generycznych chciałbym użyć kodu, który opublikowałem.Co powiesz na to:
Zastosowanie tej
static object.Equals()
metody pozwala uniknąć koniecznościnull
samodzielnego sprawdzania. Jawne kwalifikowanie wywołaniaobject.
prawdopodobnie nie jest konieczne w zależności od kontekstu, ale zwykle poprzedzamstatic
wywołania nazwą typu, aby kod był bardziej rozpuszczalny.źródło
Udało mi się znaleźć artykuł Microsoft Connect, który szczegółowo omawia ten problem:
Oto, co możesz zrobić ...
Zweryfikowałem, że obie te metody działają w celu ogólnego porównania typów referencyjnych i typów wartości:
lub
Aby wykonać porównania z operatorem „==”, musisz użyć jednej z następujących metod:
Jeśli wszystkie przypadki T wywodzą się ze znanej klasy bazowej, możesz poinformować kompilator o ograniczeniach typów ogólnych.
Następnie kompilator rozpoznaje sposób wykonywania operacji
MyBase
i nie wyrzuci błędu „Operator” == ”, którego nie można zastosować do argumentów typu„ T ”i„ T ”, które widzisz teraz.Inną opcją byłoby ograniczenie T do dowolnego typu, który implementuje
IComparable
.A następnie użyj
CompareTo
metody zdefiniowanej przez interfejs IComparable .źródło
Spróbuj tego:
które powinny się skompilować i robić, co chcesz.
źródło
Equals
MetodaIEqualityComparer
pobiera dwa argumenty, dwa przedmioty do porównania, więc nie, to nie jest zbędny.(Edytowane)
Marc Gravell ma najlepszą odpowiedź, ale chciałem opublikować prosty fragment kodu, który opracowałem, aby go zademonstrować. Po prostu uruchom to w prostej aplikacji na konsolę C #:
Jeszcze jedno: czy ktoś z VS2008 może wypróbować to jako metodę rozszerzenia? Utknąłem tutaj w 2005 roku i jestem ciekawy, czy byłoby to dozwolone.
Edycja: Oto, jak to zrobić jako metodę rozszerzenia:
źródło
Aby obsłużyć wszystkie typy T, w tym te, gdzie T jest typem pierwotnym, musisz skompilować obie metody porównania:
źródło
Będzie tu problem -
Jeśli chcesz, aby działało to dla dowolnego typu, wartość domyślna (T) będzie zawsze pusta dla typów referencyjnych, a 0 (lub struct pełna 0) dla typów wartości.
Prawdopodobnie nie jest to zachowanie, którego szukasz. Jeśli chcesz, aby działało to w sposób ogólny, prawdopodobnie musisz użyć refleksji, aby sprawdzić typ T i obsłużyć typy wartości inne niż typy referencyjne.
Alternatywnie, możesz nałożyć na to ograniczenie interfejsu, a interfejs może zapewnić sposób sprawdzenia domyślnego ustawienia klasy / struct.
źródło
Myślę, że prawdopodobnie musisz podzielić tę logikę na dwie części i najpierw sprawdzić, czy nie ma wartości null.
W metodzie IsNull opieramy się na fakcie, że obiekty ValueType z definicji nie mogą mieć wartości NULL, więc jeśli wartość przypadkowo jest klasą wywodzącą się z ValueType, wiemy już, że nie jest ona pusta. Z drugiej strony, jeśli nie jest to typ wartości, możemy po prostu porównać wartość rzutowaną na obiekt z wartością null. Moglibyśmy uniknąć sprawdzania wartości ValueType, przechodząc prosto do rzutowania na obiekt, ale oznaczałoby to, że typ wartości zostałby zapakowany w pole, czego prawdopodobnie chcemy uniknąć, ponieważ oznacza to, że na stosie tworzony jest nowy obiekt.
W metodzie IsNullOrEmpty sprawdzamy specjalny przypadek ciągu. Dla wszystkich innych typów porównujemy wartość (która już wie, że nie jest pusta) z wartością domyślną, która dla wszystkich typów referencyjnych jest pusta, a dla typów wartości jest zwykle jakąś formą zera (jeśli są integralne).
Korzystając z tych metod, następujący kod zachowuje się tak, jak można się spodziewać:
źródło
Metoda rozszerzenia oparta na zaakceptowanej odpowiedzi.
Stosowanie:
Na przemian z null, aby uprościć:
Stosowanie:
źródło
Używam:
źródło
Nie wiem, czy to działa z twoimi wymaganiami, czy nie, ale możesz ograniczyć T do typu, który implementuje interfejs, taki jak IComparable, a następnie użyć metody ComparesTo () z tego interfejsu (który IIRC obsługuje / obsługuje wartości zerowe) w ten sposób :
Prawdopodobnie są też inne interfejsy, których można by użyć IEquitable itp.
źródło
@ilitirit:
Operator „==” nie może być stosowany do argumentów typu „T” i „T”
Nie mogę wymyślić sposobu, aby to zrobić bez jawnego testu zerowego, a następnie wywołania metody lub obiektu Equals.Equals, jak sugerowano powyżej.
Możesz opracować rozwiązanie za pomocą System.Comparison, ale tak naprawdę skończy się to znacznie większą liczbą linii kodu i znacznie zwiększy złożoność.
źródło
Myślę, że byłeś blisko.
Teraz to się kompiluje, ale jeśli się nie powiedzie
myArgument
ma wartość null, co jest częścią tego, co testuję. Mogę dodać jawny czek zerowy w następujący sposób:Wystarczy odwrócić obiekt, na którym wzywa się równych, aby uzyskać eleganckie podejście zerowe.
źródło