Подтвердить что ты не робот

Сравнение структур с нулевым

Возможный дубликат:
С# в порядке со сравнением типов значений с нулем

Я работал над приложением 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, которые не могут быть нулевыми, поэтому почему это внезапно неожиданно?

4b9b3361

Ответ 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 в этом случае пропустила это).

В зависимости от вашей реализации вашей структуры (и того, что она представляет) сравнение с нулем может быть совершенно допустимым, поэтому это возможно.