Сравнивая общий с нулевым значением, которое может быть значением или ссылочным типом? - PullRequest
41 голосов
/ 11 января 2012
public void DoFoo<T>(T foo) where T : ISomeInterface<T>
{
    //possible compare of value type with 'null'.
    if (foo == null) throw new ArgumentNullException("foo");
}

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

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

Ответы [ 2 ]

53 голосов
/ 11 января 2012

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

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

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

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

Нет. Сравнение с нулем - это то, что нужно делать здесь.

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

Есть ли вероятность возникновения проблемы из-за этого?

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

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

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

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

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

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

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

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

11 голосов
/ 11 января 2012

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

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
}
...