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

С# Nullable Equality Operations, Почему null <= null разрешает как false?

Почему это в .NET

null >= null

разрешается как false, но

null == null 

разрешается как истина?

Другими словами, почему null >= null эквивалентно null > null || null == null?

Есть ли у кого официальный ответ?

4b9b3361

Ответ 1

Это поведение определено в спецификации С# (ECMA-334) в разделе 14.2.7 (я выделил соответствующую часть):

Для реляционных операторов

< > <= >=

существует снятая форма оператора, если типы операндов являются нечетными типами значений и если тип результата bool. Поднятая форма строится путем добавления единственного модификатора ? к каждому типу операнда. Поднятый оператор выражает значение false, если один или оба операнда null. В противном случае снятый подъемник оператора операнды и применяет основной оператор для получения результата bool.

В частности, это означает, что обычные законы отношений не выполняются; x >= y не означает !(x < y).

Сведения о горах

Некоторые люди спрашивают, почему компилятор решает, что это оператор с отменой для int? в первую очередь. Давайте посмотрим.:)

Начнем с 14.2.4, "Разрешение перегрузки двоичных операторов". Здесь подробно описаны шаги, которые следует выполнить.

  • Во-первых, определяемые пользователем операторы проверяются на пригодность. Это делается путем изучения операторов, определенных типами с каждой стороны >=..., что ставит вопрос о том, что такое тип null! Литерал null фактически не имеет никакого типа, пока не будет дан его, это просто "нулевой литерал". Следуя указаниям под 14.2.5, мы обнаруживаем, что здесь нет подходящих операторов, поскольку нулевой литерал не определяет каких-либо операторов.

  • Этот шаг дает нам возможность изучить набор предопределенных операторов для пригодности. (В этом разделе также исключаются перечисления, так как ни одна из них не является типом перечисления.) Соответствующие предопределенные операторы перечислены в разделах с 14.9.1 по 14.9.3, и все они являются операторами с примитивными числовыми типами, а также отмененными версиями эти операторы (заметим, что операторы string здесь не включены).

  • Наконец, мы должны выполнить разрешение перегрузки с помощью этих операторов и правил в 14.4.2.

Фактически выполнение этого разрешения было бы очень утомительным, но, к счастью, есть ярлык. В разделе 14.2.6 приведено информативное представление результатов разрешения перегрузки, в котором говорится:

... рассмотрим предопределенные реализации двоичного * оператора:

int operator *(int x, int y);
uint operator *(uint x, uint y);
long operator *(long x, long y);
ulong operator *(ulong x, ulong y);
void operator *(long x, ulong y);
void operator *(ulong x, long y);
float operator *(float x, float y);
double operator *(double x, double y);
decimal operator *(decimal x, decimal y);

Когда к этому набору операторов применяются правила разрешения перегрузки (§14.4.2), следует выбрать первый из операторы, для которых неявные преобразования существуют из типов операндов.

Так как обе стороны null, мы можем сразу выбросить все непереработанные операторы. Это оставляет нас с поднятыми числовыми операторами на всех примитивных числовых типах.

Затем, используя предыдущую информацию, мы выбираем первый из операторов, для которых существует неявное преобразование. Поскольку нулевой литерал неявно конвертируется в тип с нулевым значением, а для int существует нулевой тип, мы выбираем первый оператор из списка, который равен int? >= int?.

Ответ 2

Ряд ответов обращается к спецификации. В том, что оказывается необычным поворотом событий, спецификация С# 4 не оправдывает поведение, конкретно упомянутое о сравнении двух нулевых литералов. На самом деле, строгое чтение спецификации говорит о том, что "null == null" должно приводить к ошибке неоднозначности! (Это связано с ошибкой редактирования, сделанной при очистке спецификации С# 2 при подготовке к С# 3, и авторы спектакля не намерены делать это незаконным.)

Прочтите внимательно, если вы мне не верите. В нем говорится, что существуют операторы равенства, определенные по int, uint, long, ulong, bool, decimal, double, float, string, перечислениям, делегатам и объектам, плюс версии с отмененными значениями для всех операторов типов значений.

Теперь у нас есть проблема; это множество бесконечно велико. На практике мы не формируем бесконечный набор всех операторов для всех возможных типов делегирования и перечисления. Спецификация должна быть исправлена ​​здесь, чтобы отметить, что единственными операторами перечисления и типов делегатов, которые добавляются к наборам кандидатов, являются те из перечисляемых или типов делегатов, которые являются типами любого аргумента.

Пусть поэтому оставляют из него перечисляемые и делегированные типы, поскольку ни один из аргументов не имеет типа.

Теперь мы имеем проблему разрешения перегрузки; мы должны сначала устранить все неприменимые операторы, а затем определить наилучшие из применимых операторов.

Ясно, что операторы, определенные для всех типов значений, не допускающих нулевых значений, неприменимы. Это оставляет операторов по типам значений с нулевым значением, а также по строкам и объектам.

Теперь мы можем устранить некоторые из-за причин "блеска". Лучший оператор - тот, у которого более конкретные типы. Int? является более конкретным, чем любой из других нулевых числовых типов, поэтому все они устранены. Строка более специфична, чем объект, поэтому объект исключается.

Остается оператор равенства для string, int? и bool? как применимые операторы. Какой из них лучше? Ни один из них не лучше другого. Поэтому это должно быть ошибкой двусмысленности.

Для того, чтобы это оправдано спецификацией, нам нужно будет отменить спецификацию, чтобы отметить, что "null == null" определяется как имеющее семантику равенства строк, и что это константа времени компиляции true.

Я фактически только что открыл этот факт вчера; как странно, что вы должны спросить об этом.

Чтобы ответить на вопросы, заданные в других ответах, почему null >= null дает предупреждение о сравнении с int? - ну, примените тот же анализ, что и я. Операторы >= по типам непустых значений неприменимы, а из тех, которые остались, оператор на int? является лучшим. Не существует ошибки двусмысленности для >=, потому что на bool нет оператора >=? или строки. Компилятор правильно анализирует оператор как сравнение двух значений с нулевыми значениями.

Чтобы ответить на более общий вопрос о том, почему операторы с нулевыми значениями (в отличие от литералов) имеют особое необычное поведение, см. мой ответ на дублированный вопрос. В нем четко объясняются критерии проектирования, которые оправдывают это решение. Короче: операции с нулевым значением должны иметь семантику операций над "Я не знаю". Является ли количество, которое вы не знаете больше или равно другому количеству, которое вы не знаете? Единственный разумный ответ: "Я не знаю!" Но мы должны превратить это в bool, и разумный bool является "ложным". Но при сравнении для равенства большинство людей думает, что значение null должно быть равно нулю, хотя сравнение двух вещей, которые вы не знаете для равенства, также должно приводить к "Я не знаю". Это дизайнерское решение является результатом отторжения множества нежелательных результатов друг к другу, чтобы найти наименее плохую, которая делает работу этой функции; это делает язык несколько непоследовательным, я согласен.

Ответ 3

Компилятор выводит, что в случае операторов сравнения null неявно вводится как int?.

Console.WriteLine(null == null); // true
Console.WriteLine(null != null); // false
Console.WriteLine(null < null);  // false*
Console.WriteLine(null <= null); // false*
Console.WriteLine(null > null);  // false*
Console.WriteLine(null >= null); // false*

Visual Studio предлагает предупреждение:

* Сравнение с нулевым типом 'int?' всегда производит "false"

Это можно проверить с помощью следующего кода:

static void PrintTypes(LambdaExpression expr)
{
    Console.WriteLine(expr);
    ConstantExpression cexpr = expr.Body as ConstantExpression;
    if (cexpr != null)
    {
        Console.WriteLine("\t{0}", cexpr.Type);
        return;
    }
    BinaryExpression bexpr = expr.Body as BinaryExpression;
    if (bexpr != null)
    {
        Console.WriteLine("\t{0}", bexpr.Left.Type);
        Console.WriteLine("\t{0}", bexpr.Right.Type);
        return;
    }
    return;
}
PrintTypes((Expression<Func<bool>>)(() => null == null)); // constant folded directly to bool
PrintTypes((Expression<Func<bool>>)(() => null != null)); // constant folded directly to bool
PrintTypes((Expression<Func<bool>>)(() => null < null));
PrintTypes((Expression<Func<bool>>)(() => null <= null));
PrintTypes((Expression<Func<bool>>)(() => null > null));
PrintTypes((Expression<Func<bool>>)(() => null >= null));

Выходы:

() => True
        System.Boolean
() => False
        System.Boolean
() => (null < null)
        System.Nullable`1[System.Int32]
        System.Nullable`1[System.Int32]
() => (null <= null)
        System.Nullable`1[System.Int32]
        System.Nullable`1[System.Int32]
() => (null > null)
        System.Nullable`1[System.Int32]
        System.Nullable`1[System.Int32]
() => (null >= null)
        System.Nullable`1[System.Int32]
        System.Nullable`1[System.Int32]

Почему?

Это кажется логичным для меня. Прежде всего, здесь соответствующие разделы С# 4.0 Spec.

Нулевой литерал §2.4.4.6:

Нулевой литерал может быть неявно преобразован в ссылочный тип или тип с нулевым значением.

Двоичные числовые акции §7.3.6.2:

Двоичное числовое продвижение происходит для операндов предопределенных +, -, *,/,%, &, |, ^, ==,! =, > , <, >= и <= двоичных операторов, Двоичное числовое продвижение неявно преобразует оба операнда в общий тип, который в случае нереляционных операторов также становится типом результата операции. Двоичное числовое продвижение состоит из применения следующих правил в следующем порядке:

• Если один из операндов имеет тип decimal, другой операнд преобразуется в тип десятичного или возникает ошибка привязки, если другой операнд имеет тип float или double.
• В противном случае, если любой из операндов имеет тип double, другой операнд преобразуется в тип double.
• В противном случае, если любой операнд имеет тип float, другой операнд преобразуется в тип float.
• В противном случае, если любой из операндов имеет тип ulong, другой операнд преобразуется в тип ulong, или возникает ошибка времени привязки, если другой операнд имеет тип sbyte, short, int или long.
• В противном случае, если любой из операндов имеет тип long, другой операнд преобразуется в тип long.
• В противном случае, если любой операнд имеет тип uint, а другой операнд имеет тип sbyte, short или int, оба операнда преобразуются в тип long.
• В противном случае, если любой операнд имеет тип uint, другой операнд преобразуется в тип uint.
• В противном случае оба операнда преобразуются в тип int.

Поднятые операторы §7.3.7:

Поднятые операторы допускают предопределенные и определяемые пользователем операторы, которые работают с типами значений, не допускающих нулевых значений, которые также могут использоваться с типами NULL для этих типов. Поднятые операторы построены из предопределенных и определяемых пользователем операторов, которые отвечают определенным требованиям, как описано в следующем:

• Для реляционных операторов < > <= >=
если тип операндов является невообразимыми типами значений, а тип результата - bool. Поднятая форма строится путем добавления сингла? модификатора для каждого типа операнда. Поднятый оператор выдает значение false, если один или оба операнда равны нулю. В противном случае, снятый оператор разворачивает операнды и применяет основной оператор для получения результата bool.

У единственного нуль-литерала действительно нет типа. Это определяется тем, на что он назначен. Однако назначение не имеет места. Только учитывая встроенные типы с поддержкой языка (с ключевыми словами), object или любая нулевая, будет хорошим кандидатом. Однако object не сопоставим, поэтому его исключают. Это оставляет нулевых типов хорошими кандидатами. Но какой тип? Поскольку ни левый, ни правый операнды не имеют определенного типа, они по умолчанию преобразуются в (с нулевым) значением int. Поскольку оба значения с нулевым значением равны null, он возвращает false.

Ответ 4

Кажется, что компилятор обрабатывает значение null, как если бы они были целыми типами. Замечания VS2008: "Comparing with null of type 'int?' always produces 'false'"

Ответ 5

Когда я запустил

null >= null

Я получаю предупреждение:

Сравнение с нулевым типом 'int?' всегда производит "false"

Интересно, почему он был добавлен в int, хотя.

Ответ 6

Это потому, что компилятор достаточно умен, чтобы понять, что >= null всегда будет ложным и заменяет ваше выражение постоянным значением false.

Посмотрите этот пример:

using System;

class Example
{
    static void Main()
    {
        int? i = null;

        Console.WriteLine(i >= null);
        Console.WriteLine(i == null);
    }
}

Это сводится к следующему коду:

class Example
{
    private static void Main()
    {
        int? i = new int?();
        Console.WriteLine(false);
        Console.WriteLine(!i.HasValue);
    }
}

Ответ 7

Это не "официальный" ответ, это мое лучшее предположение. Но если вы имеете дело с нулевыми ints и сравниваете их, вы, скорее всего, всегда хотите, чтобы сравнение возвращало false, если вы имеете дело с двумя "int?" S, которые являются нулевыми. Таким образом, если он вернёт true, вы можете быть уверены, что действительно сравнили два целых числа, а не два нулевые значения. Это просто устраняет необходимость в отдельной нулевой проверке.

Тем не менее, это потенциально сбивает с толку, если это не то поведение, которое вы ожидаете!

Ответ 8

Поведение зарегистрировано на этой странице о Nullable Types. Это не дает реального объяснения причин, но моя интерпретация такова. > и < не имеют значения, когда в отношении null. null - это отсутствие значения и, следовательно, равно только другой переменной, которая также не имеет значения. Поскольку для > и < нет значения, которое переносится на >= и <=.

Ответ 9

Они, кажется, смешивают парадигмы с C и SQL.

В контексте нулевых переменных null == null должен действительно давать false, поскольку равенство не имеет смысла, если ни одно значение не известно, однако выполнение этого для С# как целого может вызвать проблемы при сравнении ссылок.

Ответ 10

Короткий ответ будет "потому, что способ, которым эти операторы определены в спецификации".

Из раздела 8.19 ECMA С# spec:

Поднятые формы == и !=операторы рассматривают два нулевых значения равным и нулевым значением, не равным ненулевое значение. Поднятые формы <, >, <= и >=return false, если один или оба операнда являются нулевыми.

Ответ 11

Этот вопрос был Deja vu, о, подождите, пока он...

Почему >= возвращает false, когда == возвращает true для нулевых значений?

То, что я помню из другого ответа, было:

Потому что равенство определяется отдельно от сопоставимости. Вы можете проверить x == null, но x > null не имеет смысла. В С# он всегда будет ложным.