Как вернуть пустое значение вместо нуля в С#

У меня есть расширение для строковых массивов, которое возвращает пустую строку, если массив имеет значение null, если длина массива == 1, он возвращает первый элемент, в противном случае возвращается элемент в позиции индекса, без проверки границ.

public static Strings IndexOrDefault(this String[] arr, Int32 index)
{
    if (arr == null)
        return String.Empty;
    return arr.Length == 1 ? arr[0] : arr[index];
}

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

public static T IndexOrDefault<T>(this T[] arr, Int32 index)
{
    if (arr == null)
        return default(T);
    return arr.Length == 1 ? arr[0] : arr[index];
}

И все было хорошо, за исключением того, что по умолчанию (String) это не String.Empty, а null. Итак, теперь я возвращаю нули для строк, что было первым требованием...

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

Редактировать 2: (первое было определение)

Я использую этот подход сейчас, который работает, но он не так элегантен, как хотелось бы (ну, метод тоже не элегантен, но решение может :))

public static T IndexOrDefault<T>(this T[] arr, Int32 index)
{
    if (arr == null)
        return default(T);
    return arr.Length == 1 ? arr[0] : arr[index];
}

public static String IndexOrDefault(this String[] arr, Int32 index)
{
    if (arr == null)
        return String.Empty;
    return arr.Length == 1 ? arr[0] : arr[index];
}

Два разных метода: один для String и общий для остальных.


person Eugenio Miró    schedule 04.07.2011    source источник
comment
Возврат чего-либо кроме null из метода с именем WhateverOrDefault для ссылочного типа был бы запутанным, неинтуитивным и, честно говоря, откровенной ложью. Ваш метод говорит одно, вы делаете другое. null используется по умолчанию для string, а не String.Empty. Кроме того, зачем проглатывать ошибку, возвращая array[0], если длина равна 1? Если вызывающий передает неверный индекс, он должен знать об этом, а не тратить время (потенциально) на поиск скрытой ошибки.   -  person Ed S.    schedule 05.07.2011
comment
Да, вы правы, этот код был портирован из asp (с JS на стороне сервера), и он нуждается в некотором рефакторинге здесь и там... но кто-то должен за это платить :)   -  person Eugenio Miró    schedule 05.07.2011


Ответы (6)


Нет общего значения по умолчанию, кроме null для ссылочных типов и некоторого варианта 0 для типов значений.

Вы можете легко предоставить один, хотя

public static T IndexOrDefault<T>(this T[] arr, Int32 index, T theDefaultValue)
{
    if (arr == null)
        return theDefaultValue;
    return arr.Length == 1 ? arr[0] : arr[index];
}

public static T IndexOrDefault<T>(this T[] arr, Int32 index, Func<T> defaultFactory)
{
    if (arr == null)
        return defaultFactory();
    return arr.Length == 1 ? arr[0] : arr[index];
}
person cordialgerm    schedule 04.07.2011
comment
Хороший ответ. Два предложения: во-первых, не называйте свой параметр default, так как это зарезервированное слово. Во-вторых, сделайте T defaultValue необязательным параметром, чтобы у вас был более приятный API и вам нужно было указывать значение по умолчанию только тогда, когда вам нужно его переопределить. - person mattmc3; 05.07.2011
comment
Кроме того, следует отметить, что если вы передаете значение по умолчанию для типа объекта, вам придется создать новый, даже если он вам не понадобится, что может быть дорогостоящим. Я предполагаю, что именно поэтому вы допустили альтернативу defaultFactory. Другой подход заключается в использовании оператора объединения ?? в вызывающем объекте и полном исключении переопределения по умолчанию из реализации метода. Это был бы более последовательный и идиоматический вариант, который лучше соответствует использованию остальных встроенных ...OrDefault() методов фреймворка. - person mattmc3; 05.07.2011
comment
@mattmc3 Спасибо за совет по умолчанию. Правильно, реализация defaultFactory предназначена для ситуаций, когда создание нового T() дорого или не требуется часто. Я не указал значение по умолчанию для параметра, потому что не уверен, какую версию С# использует OP. - person cordialgerm; 05.07.2011
comment
@mattmc3 проблема с использованием ?? заключается в том, что в индексе массива может храниться нуль. В этой ситуации у вас не было бы возможности узнать, был ли нуль, выходящий из функции, потому что массив равен нулю или потому что нулевое значение было расположено по указанному индексу - person cordialgerm; 05.07.2011
comment
Это решение подразумевало бы изменение всего кода, который у меня уже есть, из-за третьего параметра, который здесь не применим. Кстати, хороший ответ, спасибо! - person Eugenio Miró; 05.07.2011

или первый элемент, если в массиве нет элемента в позиции индекса

Не совсем. Метод возвращает первый элемент, если длина массива равна единице, но вообще не проверяет индекс. То, что вы описываете, скорее будет примерно таким:

public static Strings IndexOrDefault(this String[] arr, Int32 index) {
  if (arr == null || arr.Length == 0) return String.Empty;
  if (index >= arr.Length) return arr[0];
  return arr[index];
}

Общая версия будет:

public static T IndexOrDefault<T>(this T[] arr, Int32 index) {
  if (arr == null || arr.Length == 0) return default(T);
  if (index >= arr.Length) return arr[0];
  return arr[index];
}

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

Нет, нет способа получить пустое значение для универсального типа. Большинство типов вообще не имеют пустого значения. Например, int не может быть пустым.

person Guffa    schedule 04.07.2011
comment
Вы тоже правы, спасибо за комментарий, перепишу определение, исходный код не проверял и портировал как есть. - person Eugenio Miró; 05.07.2011

Нет, null — это значение по умолчанию для string. Вам потребуется либо добавить код, специально проверяющий, является ли T строкой, либо использовать другой подход.

person Jonathan Wood    schedule 04.07.2011

Вы можете попробовать вернуть new T(), но вам нужно добавить ограничение where T : new в объявление метода.

В противном случае строка по умолчанию равна нулю. Итак, следующий вариант — создать перегрузку строк... Я знаю, что это не лучший вариант, но вы можете с ним справиться.

person Anže Vodovnik    schedule 04.07.2011
comment
Обратите внимание, что такой подход не будет работать для типов значений, например. Int... Так что лично я бы предпочел проверить, является ли T строкой, и явно создать String.Empty для возврата. - person Anže Vodovnik; 05.07.2011
comment
Я не разбираюсь в компиляторе, но не могли бы вы сделать второе переопределение where T : struct для типов значений? - person mattmc3; 05.07.2011

Вы можете проверить наличие «специального» типа string. Также ваш код не делает то, что вы сказали, он будет делать - исправлены некоторые граничные условия для вас:

public static T IndexOrDefault<T>(this T[] arr, Int32 index)
{
    if (arr == null || arr.Length == 0)
        return default(T);

    return arr.Length <= index ? arr[0] : arr[index];
}
person BrokenGlass    schedule 04.07.2011

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

person Richard B    schedule 05.07.2011
comment
вы можете увидеть это здесь (msdn.microsoft.com/en-us/library/ xwth0h0d.aspx), но резюмируя: для ссылочных значений default(T) имеет значение null, для числовых значений — 0, для структур все значения устанавливаются равными 0 или null в зависимости от того, являются ли они ссылочными или числовыми типами. - person Eugenio Miró; 05.07.2011
comment
Хммм.. Может быть, он мог бы использовать вызов activator.createinstance(), приведя его к T для возврата? - person Richard B; 05.07.2011