Сравнение структур с нулевым
Возможный дубликат:
С# в порядке со сравнением типов значений с нулем
Я работал над приложением Windows в многопоточной среде и иногда получал исключение "Invoke или BeginInvoke не могут быть вызваны в элементе управления до тех пор, пока не будет создан дескриптор окна". Поэтому я решил, что просто добавлю эту строку кода:
if(this.Handle != null)
{
//BeginInvokeCode
}
Но это не решило проблему. Поэтому я немного вырыл и понял, что IntPtr (тип, который Form.Handle есть) является структурой, которая не может быть нулевой. Это было исправление, которое сработало:
if(this.Handle != IntPtr.Zero)
{
//BeginInvokeCode
}
Итак, он ударил меня, почему он даже скомпилировался, когда я проверял его на null? Поэтому я решил попробовать сам:
public struct Foo { }
а затем:
static void Main(string[] args)
{
Foo f = new Foo();
if (f == null) { }
}
и, конечно же, он не компилировал, говоря, что "Error 1 Operator '==' не может применяться к операндам типа" ConsoleApplication1.Foo "и" ". Итак, я начал изучать метаданные для IntPtr и начал добавлять все к моей структуре Foo, которая была там в структуре IntPtr (ISerializable, ComVisible), но ничего не помогло. Наконец, когда я добавил перегрузку оператора == и! =, Он работал:
[Serializable]
[ComVisible(true)]
public struct Foo : ISerializable
{
#region ISerializable Members
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
throw new NotImplementedException();
}
#endregion
public override bool Equals(object obj)
{
return base.Equals(obj);
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public static bool operator ==(Foo f1, Foo f2) { return false; }
public static bool operator !=(Foo f1, Foo f2) { return false; }
}
Это окончательно скомпилировано:
static void Main(string[] args)
{
Foo f = new Foo();
if (f == null) { }
}
Мой вопрос - почему? Почему, если вы переопределяете == и! =, Вам разрешено сравнивать с null? Параметры == и!= По-прежнему имеют тип Foo, которые не могут быть нулевыми, поэтому почему это внезапно неожиданно?
Ответы
Ответ 1
Похоже, проблема заключается в том, что когда MS вводила типы с нулевым значением, они делали это так, что каждая структура неявно преобразуется в свой тип с нулевым значением (foo?
), поэтому код
if( f == null)
эквивалентно
if ( (Nullable<foo>)f == (Nullable<foo>)null)
Поскольку MSDN заявляет, что "любые определяемые пользователем операторы, которые существуют для типов значений, могут также использоваться типами с нулевым значением", когда вы переопределяете operator==
, вы допускаете компиляцию неявного приведения, поскольку теперь у вас есть определяемая пользователем == - дает вам полную допустимую перегрузку.
В стороне:
Похоже, в вашем примере есть некоторая оптимизация компилятора
Единственное, что испускает компилятор, который даже намекает на то, что это тест, - это IL:
ldc.i4.0
ldc.i4.0
ceq
stloc.1 //where there is an unused boolean local
Обратите внимание, что если вы измените main на
Foo f = new Foo();
object b = null;
if (f == b) { Console.WriteLine("?"); }
Он больше не компилируется. Но если вы вставляете struct:
Foo f = new Foo();
object b = null;
if ((object)f == b) { Console.WriteLine("?"); }
если компилирует, испускает IL и работает как ожидалось (структура никогда не является нулевой);
Ответ 2
Это не имеет никакого отношения к сериализации или COM - поэтому стоит удалить это из уравнения. Например, здесь короткая, но полная программа, которая демонстрирует проблему:
using System;
public struct Foo
{
// These change the calling code correctness
public static bool operator ==(Foo f1, Foo f2) { return false; }
public static bool operator !=(Foo f1, Foo f2) { return false; }
// These aren't relevant, but the compiler will issue an
// unrelated warning if they're missing
public override bool Equals(object x) { return false; }
public override int GetHashCode() { return 0; }
}
public class Test
{
static void Main()
{
Foo f = new Foo();
Console.WriteLine(f == null);
}
}
Я считаю, что это компилируется, потому что существует неявное преобразование из нулевого литерала в Nullable<Foo>
, и вы можете сделать это на законных основаниях:
Foo f = new Foo();
Foo? g = null;
Console.WriteLine(f == g);
Интересно, что это происходит только тогда, когда == перегружен - Марк Гравелл заметил это раньше. Я не знаю, действительно ли это ошибка компилятора, или просто что-то очень тонкое в том, как разрешаются преобразования, перегрузки и т.д.
В некоторых случаях (например, int
, decimal
) компилятор будет предупреждать вас о неявном преобразовании, но в других (например, Guid
) это не так.
Ответ 3
Все, что я могу думать, это то, что ваша перегрузка оператора == дает компилятору выбор между:
public static bool operator ==(object o1, object o2)
и
public static bool operator ==(Foo f1, Foo f2)
и чтобы оба из них могли выбирать из них, можно отбросить левое на объект и использовать первое. Конечно, если вы попытаетесь запустить что-то на основе вашего кода, он не перейдет в вашу операционную перегрузку. Без выбора между операторами компилятор явно проводит дополнительную проверку.
Ответ 4
Я считаю, что когда вы перегружаете оператора, вы явно подписываетесь на то, что вы будете обрабатывать всю логику, необходимую для конкретного оператора. Следовательно, вы несете ответственность за обращение с нулевым значением в методе перегрузки оператора, если его когда-либо ударяют. В этом случае, как я уверен, вы, вероятно, заметили, что перегруженные методы никогда не попадают, если сравнивать с нулевым.
Интересно, что после Henks ответьте здесь, я проверил следующий код в рефлекторе.
Foo f1 = new Foo();
if(f1 == null)
{
Console.WriteLine("impossible");
}
Console.ReadKey();
Вот что показал рефлектор.
Foo f1 = new Foo();
Console.ReadKey();
Компилятор очищает его, и поэтому перегруженные методы оператора даже не вызываются.
Ответ 5
struct не определяет перегрузки "==" или "! =", поэтому вы получили исходную ошибку. После того как перегрузки были добавлены в вашу структуру, сравнение было законным (с точки зрения компилятора). Поскольку создателем перегрузки оператора является ваша ответственность за обработку этой логики (очевидно, Microsoft в этом случае пропустила это).
В зависимости от вашей реализации вашей структуры (и того, что она представляет) сравнение с нулем может быть совершенно допустимым, поэтому это возможно.
Ответ 6
Я рекомендую вам взглянуть на эти страницы:
http://www.albahari.com/valuevsreftypes.aspx
http://msdn.microsoft.com/en-us/library/s1ax56ch.aspx
http://msdn.microsoft.com/en-us/library/490f96s2.aspx