Почему TimeSpan и Guid Struct сравниваются с нулем? - PullRequest
18 голосов
/ 04 августа 2009

Я заметил, что некоторые структуры .NET можно сравнить с нулем. Например:

  TimeSpan y = new TimeSpan();
        if (y == null)
            return;

прекрасно скомпилируется (то же самое со структурой Guid).
Теперь я знаю, что структуры являются типом значений и что приведенный выше код не должен компилироваться, если только не существует перегрузки оператора ==, который принимает объект. Но, насколько я могу судить, нет.
Я посмотрел на класс с Reflector, а также на документы по MSDN.
Два из них реализуют следующие интерфейсы:

IComparable, IComparable<T>, IEquatable<T>

но попытка реализовать те же интерфейсы, похоже, не помогла:

struct XX : IComparable, IComparable<XX>, IEquatable<XX> {
    public int CompareTo(Object obj) {
        return 0;
    }
    public int CompareTo (XX other){
        return 0;
    }
    public bool Equals (XX other){
        return false;
    }
    public override bool Equals(object value){
        return false;
    }
    public static int Compare(XX t1, XX t2){
        return 0;
    }
}

Я использую: .NET 2.0 Visual Studio 2005.

Кто-нибудь знает, в чем причина этого? Я просто пытаюсь лучше понять. Это не проблема, так как я знаю, что в любом случае я не должен сравнивать структуры с нулем.

Ответы [ 5 ]

13 голосов
/ 04 августа 2009

Это оператор ==.

Класс TimeSpan перегружен оператором равенства:

public static bool operator ==(DateTime d1, DateTime d2)
{
     return (t1._ticks == t2._ticks);
}

Само по себе это не позволяет сравнивать с null, , но ...

С появлением обнуляемых типов каждая структура неявно преобразуется в обнуляемый тип , поэтому, когда вы видите что-то вроде

TimeSpan y = new TimeSpan();
if (y == null)
    return;

Вы не видите , что это происходит:

TimeSpan y = new TimeSpan();
if ((Nullable<TimeSpan>)y == (Nullable<TimeSpan>)null)
    return;

Null получает неявное преобразование (неявное присвоение?), Но не все System.Object объекты do:

TimeSpan y = new TimeSpan();
object o = null;
if (y == o) //compiler error
    return;

Хорошо, но оператор равенства не принимает нулевые аргументы, не так ли?

Ну, msdn здесь поможет, заявив:

Предопределенные унарные и двоичные операторы и любые пользовательские операторы, которые существуют для типов значений также могут быть использованы обнуляемыми типами. Эти операторы выдают нулевое значение если [любой из] операндов нулевой; иначе, оператор использует содержащееся значение рассчитать результат.

Таким образом, вы фактически получаете реализацию с нулевым значением для каждого оператора бесплатно с фиксированным определенным поведением. Упомянутое выше «содержащееся значение» является фактическим значением, которое должен возвращать необнуляемый оператор.

7 голосов
/ 04 августа 2009

Эта проблема была эффективно введена, когда были включены обнуляемые типы. Есть неявное преобразование из TimeSpan в TimeSpan?, и есть сравнение между TimeSpan? и нулевым значением этого типа.

Компилятор выдает предупреждение для некоторых типов, что делает его более понятным, что он пытается сделать:

int x = 10;
if (x == null)
{
    Console.WriteLine();
}

выдает это предупреждение:

Test.cs(9,13): warning CS0472: The result of the expression is always 'false'
       since a value of type 'int' is never equal to 'null' of type 'int?'

Я полагаю, что мы с Марком Грэвеллом выяснили обстоятельства, при которых предупреждение дается один раз ... обидно, что оно не соответствует.

7 голосов
/ 04 августа 2009

Этот случай рассматривается для обобщений в разделе 7.9.6 спецификации языка C #.

Конструкция x == null разрешена, даже если T может представлять тип значения, а результат просто определяется как false, когда T является типом значения.

Я немного покопался в спецификации и не смог найти более общего правила. ответ Джона означает, что это проблема продвижения по службе.

Это правило (или аналогичное изменение), похоже, применяется здесь. Если вы внимательно посмотрите на отраженный вывод, вы заметите, что сравнения там нет. Компилятор C #, по-видимому, оптимизирует это сравнение и заменяет его на false.

Например, если вы введете следующее

var x = new TimeSpan();
var y = x == null;
Console.WriteLine(x);

Затем, декомпилируя его, вы увидите следующее

var x = new TimeSpan();
var y = false;
Console.WriteLine(x);
4 голосов
/ 04 августа 2009

См. Также: C # 3 (.NET 3.5) версия csc не в состоянии сообщить CS0162 о невосстановимом коде (struct / null)

Начиная с компилятора C # 3, это означает, что иногда он даже не предупреждает вас об этом; -p

Поскольку Guid / TimeSpan и т.д. предоставляют ==, они попадают в эту ловушку, где она вас не предупреждает.

3 голосов
/ 04 августа 2009

Я НАШЕЛ ЕГО :)

Следующее выдает предупреждение:

int i = 0;
if (i == null)
// ^^ Warning: The result of the expression is always 'false' since a value of
//             type 'int' is never equal to 'null' of type 'int?'

Компилятор просто не может выдать правильное предупреждение о том, что введенный вами null был преобразован в тип TimeSpan? для сравнения.

Редактировать: Связанный раздел в спецификации - §13.7.1, утверждающий, что null может быть неявно преобразован в любой обнуляемый тип, и (очень трудно читаемый) раздел §13.7.2, указывающий тип значения T может быть неявно преобразовано в T?.

То, что я изначально написал:

Что бы ни происходило, это что-то в спецификации C #, потому что, как говорит JaredPar, это просто компилируется в false.

Обратите внимание, что это не компилируется:

TimeSpan ts = new TimeSpan();
object o = null;
if (ts == o) // error, Operator '==' cannot be applied to operands of type 'System.TimeSpan' and 'object'
    ...
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...