Как я могу вернуть NULL из универсального метода в C #?


546

У меня есть общий метод с этим (фиктивным) кодом (да, я знаю, что в IList есть предикаты, но мой код использует не IList, а какую-то другую коллекцию, в любом случае это не имеет значения для вопроса ...)

static T FindThing<T>(IList collection, int id) where T : IThing, new()
{
    foreach T thing in collecion
    {
        if (thing.Id == id)
            return thing;
    }
    return null;  // ERROR: Cannot convert null to type parameter 'T' because it could be a value type. Consider using 'default(T)' instead.
}

Это дает мне ошибку сборки

«Невозможно преобразовать значение NULL в тип параметра« T », поскольку это может быть тип значения. Попробуйте вместо этого использовать« default (T) ».»

Могу ли я избежать этой ошибки?


Могут ли обнуляемые ссылочные типы (в C # 8) быть лучшим решением для этого сейчас? docs.microsoft.com/en-us/dotnet/csharp/nullable-references Возврат nullнезависимо от того, Tесть Objectили intесть char.
Александр - Восстановить Монику

Ответы:


969

Два варианта:

  • Возврат, default(T)что означает, что вы вернётесь, nullесли T является ссылочным типом (или типом значения Nullable), 0для int, '\0'для charи т. Д. ( Таблица значений по умолчанию (C # Reference) )
  • Ограничить T ссылочным типом с where T : classограничением, а затем вернуть nullкак обычно

3
Что если мой тип возвращаемого значения - enum, а не класс? Я не могу указать T: enum :(
Джастин

1
В .NET перечисление - это очень тонкая (и довольно неплотная) оболочка для целочисленного типа. Соглашение состоит в том, чтобы использовать ноль для вашего значения enum по умолчанию.
Майк Чемберлен

27
Я думаю, что проблема в том, что если вы используете этот универсальный метод, чтобы сказать, преобразуйте объект базы данных из DbNull в Int и он возвращает default (T), где T - это int, он вернет 0. Если это число на самом деле имеет смысл, то вы бы передавали неверные данные в тех случаях, когда это поле было пустым. Или лучшим примером будет DateTime. Если поле было чем-то вроде «DateClosed» и было возвращено как ноль, поскольку учетная запись по-прежнему открыта, оно фактически по умолчанию (DateTime) будет равно 1/1/0000, что означает, что учетная запись была закрыта до того, как были изобретены компьютеры.
Синастетическая

21
@Sinaesthetic: Таким образом, вы бы приняли Nullable<int>или Nullable<DateTime>вместо. Если вы используете ненулевой тип и хотите представить нулевое значение, вы просто напрашиваетесь на неприятности.
Джон Скит

1
Я согласен, я просто хотел поднять это. Я думаю, что я делал больше похоже на MyMethod <T> (); предположить, что это ненулевой тип и MyMethod <T?> (); предположить, что это обнуляемый тип. Поэтому в моих сценариях я мог бы использовать временную переменную, чтобы поймать ноль и перейти оттуда.
Синастетик

84
return default(T);

Эта ссылка: msdn.microsoft.com/en-us/library/xwth0h0d(VS.80).aspx должна объяснить, почему.
Харпер Шелби

1
Черт возьми, я бы сэкономил много времени, если бы знал об этом ключевом слове - спасибо, Рикардо!
Ана Беттс

1
Я удивлен, что это не набрало больше голосов, так как ключевое слово «по умолчанию» является более комплексным решением, позволяющим использовать не ссылочные типы в сочетании с числовыми типами и структурами. Хотя принятый ответ решает проблему (и действительно полезен), он лучше отвечает, как ограничить тип возвращаемого значения обнуляемыми / ссылочными типами.
Стив Джексон

33

Вы можете просто настроить свои ограничения:

where T : class

Тогда возврат нуля разрешен.


Спасибо. Я не могу выбрать 2 ответа в качестве принятого решения, поэтому я выбираю Джона Скита, потому что его ответ имеет два решения.
edosoft

@Migol это зависит от ваших требований. Может быть, их проект требует этого IDisposable. Да, большую часть времени это не должно быть. System.Stringне реализует IDisposable, например. Ответчик должен был это уточнить, но это не делает ответ неверным. :)
ahwm

@Migol Понятия не имею, почему у меня там есть IDisposable. Удалены.
TheSoftwareJedi

13

Добавьте ограничение класса в качестве первого ограничения к вашему универсальному типу.

static T FindThing<T>(IList collection, int id) where T : class, IThing, new()

Спасибо. Я не могу выбрать 2 ответа в качестве принятого решения, поэтому я выбираю Джона Скита, потому что его ответ имеет два решения.
edosoft

7
  1. Если у вас есть объект, то нужно выполнить Typecast

    return (T)(object)(employee);
  2. если вам нужно вернуть ноль.

    return default(T);

Привет, user725388, пожалуйста, подтвердите первый вариант
Джоги Джозеф Джордж

7

Ниже приведены два варианта, которые вы можете использовать

return default(T);

или

where T : class, IThing
 return null;

6

Другой вариант - добавить это в конец вашей декларации:

    where T : class
    where T: IList

Таким образом, это позволит вам вернуть ноль.


Если оба ограничения относятся к одному типу, вы упоминаете тип один раз и используете запятую, например where T : class, IList. Если у вас есть ограничения на разные типы, вы повторяете токен where, как в where TFoo : class where TBar : IList.
Джеппе Стиг Нильсен

3

решение TheSoftwareJedi работает,

также вы можете заархивировать его, используя пару типов value и nullable:

static T? FindThing<T>(IList collection, int id) where T : struct, IThing
{
    foreach T thing in collecion
    {
        if (thing.Id == id)
            return thing;
    }
    return null;
}

1

Примите рекомендацию об ошибке ... и либо пользователь, default(T)либоnew T .

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

В противном случае, потенциально рассмотрите выходной параметр для "совпадение найдено".


1

Вот рабочий пример для возвращаемых значений Nullable Enum:

public static TEnum? ParseOptional<TEnum>(this string value) where TEnum : struct
{
    return value == null ? (TEnum?)null : (TEnum) Enum.Parse(typeof(TEnum), value);
}

Начиная с C # 7.3 (май 2018 г.), вы можете улучшить ограничение до where TEnum : struct, Enum. Это гарантирует, что вызывающая сторона не предоставит случайно тип значения, который не является перечислением (такой как intили a DateTime).
Джеппе Стиг Нильсен

0

Еще одна альтернатива 2 ответам, представленным выше. Если вы измените свой тип возврата на object, вы можете вернуться null, и в то же время привести ненулевой возврат.

static object FindThing<T>(IList collection, int id)
{
    foreach T thing in collecion
    {
        if (thing.Id == id)
            return (T) thing;
    }
    return null;  // allowed now
}

Недостаток: для этого потребуется, чтобы вызывающий метод приводил возвращаемый объект (в ненулевом случае), что подразумевает бокс -> меньшую производительность. Я прав?
Четкое время

0

Для полноты картины полезно знать, что вы также можете сделать это:

return default;

Возвращает так же, как return default(T);


0

Для меня это работает как есть. Где именно проблема?

public static T FindThing<T>(this IList collection, int id) where T : IThing, new()
{
    foreach (T thing in collection)
    {
        if (thing.Id == id)
            return thing;
        }
    }

    return null; //work
    return (T)null; //work
    return null as T; //work
    return default(T); //work


}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.