Сравнение общего значения с null, которое может быть значением или ссылочным типом?

public void DoFoo<T>(T foo) where T : ISomeInterface<T>
{
    //possible compare of value type with 'null'.
    if (foo == null) throw new ArgumentNullException("foo");
}

Я намеренно проверяю только null, потому что я не хочу ограничивать ValueType от его default(T). Мой код компилируется и отлично работает в этом режиме (ReSharper жалуется, но не CodeAnalysis). Хотя я действительно удивляюсь:

  • Существует ли более стандартный способ справиться с этой ситуацией?
  • Есть ли вероятность возникновения проблемы из этого?
  • Что действительно происходит под капотом, когда я делаю вызов и передаю тип значения?

Ответ 1

Я намеренно проверяю только null, потому что я не хочу ограничивать значение ValueType равным его default(T)

Это хорошее понимание, но не волнуйтесь, вы уже покрыты там. Нецелесообразно сравнивать T с default(T) с использованием == в первую очередь; разрешение перегрузки не найдет уникального лучшего оператора ==.

Конечно, вы можете сделать сравнение с .Equals, но тогда вы рискуете сбой, если приемник имеет нулевое значение, чего именно вы пытаетесь избежать.

Есть ли более стандартный способ справиться с этой ситуацией?

Нет. По сравнению с нулем, это правильная вещь.

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

Есть ли вероятность выхода из этого вопроса?

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

Что действительно происходит под капотом, когда я делаю вызов и передаю тип значения?

Вопрос неоднозначен. Позвольте мне перефразировать это на два вопроса:

Что действительно происходит под капотом, когда я вызываю общий метод с аргументом типа, который является типом значения, не равным нулю?

Джиттер компилирует метод при первом вызове с этой конструкцией. Когда джиттер обнаруживает нулевую проверку, он заменяет его "false", потому что он знает, что тип значений, не имеющих значения NULL, когда-либо будет равен нулю.

Что действительно происходит под капотом, когда я вызываю общий метод с аргументом типа, который является ссылочным типом, но аргументом, который является типом структуры? Например:

interface IFoo : ISomeInterface<IFoo> {}
struct SFoo : IFoo { whatever }
...
DoFooInternal<IFoo>(new SFoo());

В этом случае джиттер не может преодолеть нулевую проверку, и сайт вызова не сможет избежать бокса. Экземпляр SFoo будет помещен в бокс, и ссылка на коробку SFoo будет проверена, чтобы увидеть, является ли она нулевым.

Ответ 2

Нет, проблем не будет, но если вы хотите, чтобы предупреждение исчезло, вы можете использовать следующее:

public void DoFoo<T>(T foo) where T : ISomeInterface<T>
{
    if (ReferenceEquals(foo, null)) throw new ArgumentNullException("foo");
}

В качестве альтернативы вы можете сделать что-то вроде этого:

// when calling this with an actual T parameter, you have to either specify the type
// explicitly or cast the parameter to T?.
public void DoFoo<T>(T? foo) where T : struct, ISomeInterface<T>
{
    if (foo == null)
    {
        // throw...
    }

    DoFooInternal(foo.Value);
}

public void DoFoo<T>(T foo) where T : class, ISomeInterface<T>
{
    if (foo == null)
    {
        // throw...
    }

    DoFooInternal(foo); 
}

private void DoFooInternal<T>(T foo) where T : ISomeInterface<T>
{
    // actual implementation
}