Сравнение с нулевым значением равно true для expr == null и expr!= Null
Я вижу что-то очень странное, чего я не могу объяснить. Я угадываю какой-то крайний случай С#, с которым я не знаком, или ошибка в среде выполнения/эмиттера?
У меня есть следующий метод:
public static bool HistoryMessageExists(DBContext context, string id)
{
return null != context.GetObject<HistoryMessage>(id);
}
Во время тестирования моего приложения я вижу, что это неверно работает - он возвращает true
для объектов, которые, как я знаю, не существуют в моем db. Поэтому я остановился на методе и в Immediate, я запустил следующее:
context.GetObject<HistoryMessage>(id)
null
null == context.GetObject<HistoryMessage>(id)
true
null != context.GetObject<HistoryMessage>(id)
true
GetObject
определяется следующим образом:
public T GetObject<T>(object pk) where T : DBObject, new()
{
T rv = Connection.Get<T>(pk);
if (rv != null)
{
rv.AttachToContext(this);
rv.IsInserted = true;
}
return rv;
}
Интересно, что при выдаче выражения в object
сравнение оценивается правильно:
null == (object)context.GetObject<HistoryMessage>(id)
true
null != (object)context.GetObject<HistoryMessage>(id)
false
Не существует переопределения оператора равенства.
Изменить: Оказывается, существует перегрузка оператора, которая была неправильной. Но тогда почему равенство правильно оценивалось во внутреннем методе generic GetObject
, где rv
имеет тип HistoryMessage
в этом случае.
public class HistoryMessage : EquatableIdentifiableObject
{
public static bool HistoryMessageExists(DBContext context, string id)
{
var rv = context.GetObject<HistoryMessage>(id);
bool b = rv != null;
return b;
}
public static void AddHistoryMessage(DBContext context, string id)
{
context.InsertObject(new HistoryMessage { Id = id });
}
}
public abstract partial class EquatableIdentifiableObject : DBObject, IObservableObject
{
public event PropertyChangedEventHandler PropertyChanged;
[PrimaryKey]
public string Id { get; set; }
//...
}
public abstract partial class EquatableIdentifiableObject
{
//...
public static bool operator ==(EquatableIdentifiableObject self, EquatableIdentifiableObject other)
{
if (ReferenceEquals(self, null))
{
return ReferenceEquals(other, null);
}
return self.Equals(other);
}
public static bool operator !=(EquatableIdentifiableObject self, EquatableIdentifiableObject other)
{
if (ReferenceEquals(self, null))
{
return !ReferenceEquals(other, null);
}
return !self.Equals(other);
}
}
public abstract class DBObject
{
[Ignore]
protected DBContext Context { get; set; }
[Ignore]
internal bool IsInserted { get; set; }
//...
}
Что здесь происходит?
Ответы
Ответ 1
- Как вы уже выяснили, оператор
==
не смог выполнить ваш тип, потому что у вас была неверная перегрузка.
- При нажатии на объект оператор
==
работал правильно, так как это была object's
реализация ==
, которая была использована, а не EquatableIdentifiableObject's
.
- В методе
GetObject
оператор правильно оценивает, потому что это не EquatableIdentifiableObject's
реализация ==
, которая используется. В С# обобщения разрешаются во время выполнения (по крайней мере, в том смысле, что здесь актуально), а не во время компиляции. Обратите внимание, что ==
является статическим, а не виртуальным. Таким образом, тип T
разрешен во время выполнения, но вызов ==
должен быть разрешен во время компиляции. Во время компиляции, когда компилятор разрешает ==
, он не будет знать, использовать EquatableIdentifiableObject's
реализацию ==
. Так как тип T имеет это ограничение: будет использоваться where T : DBObject, new()
, DBObject's
реализация (если таковая имеется). Если DBObject
не определяет ==
, тогда будет использоваться реализация первого базового класса, который делает это (до object
).
Несколько комментариев о EquatableIdentifiableObject's
реализации ==
:
- Вы можете заменить эту часть:
if (ReferenceEquals(self, null))
{
return ReferenceEquals(other, null);
}
с:
// If both are null, or both are the same instance, return true.
if (object.ReferenceEquals(h1, h2))
{
return true;
}
- Было бы более удобно заменять
public static bool operator !=(EquatableIdentifiableObject self, EquatableIdentifiableObject other)
{
...
}
с:
public static bool operator !=(EquatableIdentifiableObject self, EquatableIdentifiableObject other)
{
return !(self == other);
}
- То, как вы определяете подпись для
==
, немного вводит в заблуждение. Первый параметр имеет имя self
, а второй - other
. Это было бы нормально, если ==
был методом экземпляра. Поскольку это статический метод, имя self
немного вводит в заблуждение. Лучшими именами были бы o1
и o2
или что-то в этом направлении, чтобы два операнда обрабатывались на более равномерной основе.
Ответ 2
Может быть несколько перегрузок operator ==(...)
, как вы теперь знаете. Некоторые из них могут быть встроенными перегрузками С#, а другие могут быть определяемыми пользователем операторами.
Если вы удерживаете мышь над символом !=
или ==
в Visual Studio, он покажет вам, какая перегрузка выбрана с помощью разрешения перегрузки (вплоть до VS2013 она будет показывать только это, если выбранная перегрузка была фактически пользователем -определенный, в VS2015 он покажет его во всех случаях, которые я считаю).
привязка ==
(т.е. перегрузка для вызова) фиксируется статически во время компиляции. В этом нет ничего динамичного или виртуального. Поэтому, если у вас есть:
public T SomeMethod<T>() where T : SomeBaseClass
{
T rv = ...;
if (rv != null)
{
то какая перегрузка !=
для использования будет фиксирована во время компиляции с обычным разрешением перегрузки (включая несколько специальных правил для ==
). rv
имеет тип T
, который известен как ссылочный тип eqaul или полученный из SomeBaseClass
. Поэтому на этой основе выбирается лучшая перегрузка. Это может быть перегрузка operator !=(object, object)
(встроенная), если SomeBaseClass
не определяет (или "наследует" ) соответствующую перегрузку.
Во время выполнения, даже если фактическая подстановка для T
оказывается более конкретным типом SomeEqualityOverloadingClass
(который, конечно же, удовлетворяет ограничению), это не означает, что новое разрешение перегрузки произойдет при запуске -время!
Это отличается от метода virtual
.Equals(object)
.
В С# дженерики не работают как шаблоны, и они не похожи на dynamic
.
Если вы действительно хотите dynamic
разрешение перегрузки (привязка во время выполнения, а не во время компиляции), разрешено говорить if ((dynamic)rv != null)
.