Как правильно проверить нулевые значения?

122

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

 int y = x ?? -1;

Это здорово, за исключением случаев, когда мне нужно сделать что-нибудь простое с x. Например, если я хочу проверить Session, мне обычно приходится писать что-то более подробное.

Хотел бы я сделать это:

string y = Session["key"].ToString() ?? "none";

Но вы не можете, потому что вызывается .ToString()перед нулевой проверкой, поэтому он не работает, если Session["key"]равен нулю. В итоге я делаю вот что:

string y = Session["key"] == null ? "none" : Session["key"].ToString();

Он работает и, на мой взгляд, лучше, чем трехстрочный вариант:

string y = "none";
if (Session["key"] != null)
    y = Session["key"].ToString();

Несмотря на то, что это работает, мне все еще интересно, есть ли лучший способ. Кажется, что мне всегда приходится ссылаться Session["key"]дважды; один раз для проверки, и еще раз для задания. Любые идеи?

Чев
источник
20
Именно тогда мне хотелось бы, чтобы в C # был «оператор безопасной навигации» ( .?), как в Groovy .
Кэмерон
2
@Cameron: именно здесь я хочу, чтобы C # мог обрабатывать типы, допускающие значение NULL (включая ссылочные типы), как монаду, чтобы вам не понадобился «оператор безопасной навигации».
Jon Purdy
3
Изобретатель нулевых ссылок назвал это своей «ошибкой на миллиард долларов», и я склонен согласиться. См. Infoq.com/presentations/…
Jamie Ide
Его настоящая ошибка - небезопасное (не принудительное языковое) смешивание типов, допускающих значение NULL, и типов без метки NULL.
MSalters
@JamieIde Спасибо за очень интересную ссылку. :)
BobRodes 01

Ответы:

182

Что о

string y = (Session["key"] ?? "none").ToString();
Черный медведь
источник
79
Сила сильна с этим.
Chev
2
@Matthew: Нет, потому что значения сеанса относятся к типу Object
BlackBear
1
@BlackBear, но возвращаемое значение, скорее всего, является строкой, поэтому приведение допустимо
Firo
Это был самый прямой ответ на мой вопрос, поэтому я отмечаю ответ, но метод расширения Джона Скита .ToStringOrDefault()- мой предпочтительный способ сделать это. Однако я использую этот ответ в методе расширения Джона;)
Chev
10
Мне это не нравится, потому что, если у вас есть какой-либо другой тип объекта, заполненный в сеансе, кроме того, который вы ожидаете, вы можете скрывать некоторые тонкие ошибки в своей программе. Я бы предпочел использовать безопасный слепок, потому что думаю, что он быстрее обнаружит ошибки. Он также избегает вызова ToString () для строкового объекта.
tvanfosson
130

Если вы часто делаете это специально,ToString() то можете написать метод расширения:

public static string NullPreservingToString(this object input)
{
    return input == null ? null : input.ToString();
}

...

string y = Session["key"].NullPreservingToString() ?? "none";

Или, конечно, метод по умолчанию:

public static string ToStringOrDefault(this object input, string defaultValue)
{
    return input == null ? defaultValue : input.ToString();
}

...

string y = Session["key"].ToStringOrDefault("none");
Джон Скит
источник
16
.ToStringOrDefault()прост и элегантен. Хорошее решение.
Chev
7
Совершенно не могу с этим согласиться. Методы расширения object- это проклятие и мусор в базе кода, а методы расширения, которые работают без ошибок с нулевыми thisзначениями, являются чистым злом.
Ник Ларсен
10
@NickLarsen: Я говорю, все в меру. Методы расширения, которые работают с null, могут быть очень полезными, IMO, если они четко понимают, что делают.
Джон Скит,
3
@ one.beat.consumer: Ага. Если бы это было просто форматирование (или любые опечатки), это было бы одно, но изменение имени метода, выбранного автором, выходит за рамки обычного редактирования, IMO.
Джон Скит,
6
@ one.beat.consumer: При исправлении грамматики и опечаток это нормально, но изменение имени, которое кто-то (кто угодно, а не только я) выбрал намеренно, мне кажется другим. В этот момент я бы предложил это в комментарии.
Джон Скит,
21

Вы также можете использовать as, который дает результат в nullслучае сбоя преобразования:

Session["key"] as string ?? "none"

Это вернется, "none"даже если кто-то вставит intв Session["key"].

Andomar
источник
1
Это работает только тогда, когда вам вообще не нужно ToString().
Abel
1
Я удивлен, что никто еще не проголосовал против этого ответа. Это семантически полностью отличается от того, что хочет сделать OP.
Timwi
@Timwi: OP использует ToString()для преобразования объекта, содержащего строку, в строку. Вы можете сделать то же самое с помощью obj as stringили (string)obj. Это довольно распространенная ситуация в ASP.NET.
Andomar
5
@Andomar: Нет, OP вызывает ToString()объект (а именно Session["key"]), тип которого он не упомянул. Это может быть любой объект, не обязательно строка.
Timwi
13

Если всегда будет string, вы можете использовать:

string y = (string)Session["key"] ?? "none";

Это дает преимущество в виде жалобы вместо того, чтобы скрывать ошибку, если кто-то запихивает intчто-то в нее Session["key"]. ;)

Ry-
источник
10

Все предложенные решения хороши и отвечают на вопрос; так что это просто немного расширить его. В настоящее время большинство ответов относятся только к нулевой проверке и строковым типам. Вы можете расширить StateBagобъект, включив в него общий GetValueOrDefaultметод, аналогичный ответу, опубликованному Джоном Скитом.

Простой универсальный метод расширения, который принимает строку в качестве ключа, а затем проверяет тип объекта сеанса. Если объект имеет значение null или другой тип, возвращается значение по умолчанию, в противном случае значение сеанса возвращается строго типизированным.

Что-то вроде этого

/// <summary>
/// Gets a value from the current session, if the type is correct and present
/// </summary>
/// <param name="key">The session key</param>
/// <param name="defaultValue">The default value</param>
/// <returns>Returns a strongly typed session object, or default value</returns>
public static T GetValueOrDefault<T>(this HttpSessionState source, string key, T defaultValue)
{
    // check if the session object exists, and is of the correct type
    object value = source[key]
    if (value == null || !(value is T))
    {
        return defaultValue;
    }

    // return the session object
    return (T)value;
}
Ричард
источник
1
Можете ли вы включить образец использования этого метода расширения? Разве StateBag не занимается состоянием просмотра, а не сеансом? Я использую ASP.NET MVC 3, поэтому у меня действительно нет простого доступа к состоянию просмотра. Я думаю, вы хотите продлить HttpSessionState.
Chev
этот ответ требует получения значения 3x и 2 приведения в случае успеха. (я знаю, что это словарь, но новички могут использовать аналогичные приемы для дорогостоящих методов.)
Джейк Бергер,
3
T value = source[key] as T; return value ?? defaultValue;
Джейк Бергер
1
@jberger Приведение к значению с использованием «as» недоступно, так как нет ограничения класса для универсального типа, так как потенциально вы можете захотеть вернуть такое значение, как bool. @AlexFord Мои извинения, вы бы хотели продлить HttpSessionStateсессию. :)
Ричард
на самом деле. как заметил Ричард, требует ограничения. (... и еще один метод, если вы хотите использовать типы значений)
Джейк Бергер
7

Мы используем метод под названием NullOr.

использование

// Call ToString() if it’s not null, otherwise return null
var str = myObj.NullOr(obj => obj.ToString());

// Supply default value for when it’s null
var str = myObj.NullOr(obj => obj.ToString()) ?? "none";

// Works with nullable return values, too —
// this is properly typed as “int?” (nullable int)
// even if “Count” is just int
var count = myCollection.NullOr(coll => coll.Count);

// Works with nullable input types, too
int? unsure = 47;
var sure = unsure.NullOr(i => i.ToString());

Источник

/// <summary>Provides a function delegate that accepts only value types as return types.</summary>
/// <remarks>This type was introduced to make <see cref="ObjectExtensions.NullOr{TInput,TResult}(TInput,FuncStruct{TInput,TResult})"/>
/// work without clashing with <see cref="ObjectExtensions.NullOr{TInput,TResult}(TInput,FuncClass{TInput,TResult})"/>.</remarks>
public delegate TResult FuncStruct<in TInput, TResult>(TInput input) where TResult : struct;

/// <summary>Provides a function delegate that accepts only reference types as return types.</summary>
/// <remarks>This type was introduced to make <see cref="ObjectExtensions.NullOr{TInput,TResult}(TInput,FuncClass{TInput,TResult})"/>
/// work without clashing with <see cref="ObjectExtensions.NullOr{TInput,TResult}(TInput,FuncStruct{TInput,TResult})"/>.</remarks>
public delegate TResult FuncClass<in TInput, TResult>(TInput input) where TResult : class;

/// <summary>Provides extension methods that apply to all types.</summary>
public static class ObjectExtensions
{
    /// <summary>Returns null if the input is null, otherwise the result of the specified lambda when applied to the input.</summary>
    /// <typeparam name="TInput">Type of the input value.</typeparam>
    /// <typeparam name="TResult">Type of the result from the lambda.</typeparam>
    /// <param name="input">Input value to check for null.</param>
    /// <param name="lambda">Function to apply the input value to if it is not null.</param>
    public static TResult NullOr<TInput, TResult>(this TInput input, FuncClass<TInput, TResult> lambda) where TResult : class
    {
        return input == null ? null : lambda(input);
    }

    /// <summary>Returns null if the input is null, otherwise the result of the specified lambda when applied to the input.</summary>
    /// <typeparam name="TInput">Type of the input value.</typeparam>
    /// <typeparam name="TResult">Type of the result from the lambda.</typeparam>
    /// <param name="input">Input value to check for null.</param>
    /// <param name="lambda">Function to apply the input value to if it is not null.</param>
    public static TResult? NullOr<TInput, TResult>(this TInput input, Func<TInput, TResult?> lambda) where TResult : struct
    {
        return input == null ? null : lambda(input);
    }

    /// <summary>Returns null if the input is null, otherwise the result of the specified lambda when applied to the input.</summary>
    /// <typeparam name="TInput">Type of the input value.</typeparam>
    /// <typeparam name="TResult">Type of the result from the lambda.</typeparam>
    /// <param name="input">Input value to check for null.</param>
    /// <param name="lambda">Function to apply the input value to if it is not null.</param>
    public static TResult? NullOr<TInput, TResult>(this TInput input, FuncStruct<TInput, TResult> lambda) where TResult : struct
    {
        return input == null ? null : lambda(input).Nullable();
    }
}
Timwi
источник
Да, это более общий ответ на задуманную проблему - вы меня опередили - и кандидат на безопасную навигацию (если вы не возражаете против лямбда-выражений для простых вещей) - но писать его все же немного громоздко, хорошо :). Лично я всегда выбираю? : вместо этого (если не дорого, то все равно переставить) ...
NSGaga-в основном-неактивен
... И `` Именование '' - настоящая проблема с этим - ничто на самом деле не кажется правильным (или слишком `` добавляет '') или слишком длинным - NullOr хорошо, но слишком много внимания на `` нулевом '' ИМО (плюс вы есть ?? еще) - "Собственность" или "Сейф" - вот что я использовал. value.Dot (o => o.property) ?? @default может быть?
NSGaga-в основном-неактивный,
@NSGaga: Мы довольно долго перебирали название. Мы рассматривали, Dotно сочли это слишком неописательным. Мы NullOrвыбрали хороший компромисс между самооценкой и краткостью. Если вы действительно не заботились о наименовании, вы всегда могли его назвать _. Если вы считаете лямбды слишком громоздкими для написания, вы можете использовать для этого фрагмент, но лично я считаю это достаточно простым. Что касается ? :, вы не можете использовать это с более сложными выражениями, вам придется переместить их в новый локальный; NullOrпозволяет избежать этого.
Timwi
6

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

var y = Session["key"] as string ?? "none";

Как говорит @Jon Skeet, если вы обнаружите, что делаете это много, это метод расширения или, что еще лучше, возможно, метод расширения в сочетании со строго типизированным классом SessionWrapper. Даже без метода расширения может быть хорошей идеей использование строго типизированной оболочки.

public class SessionWrapper
{
    private HttpSessionBase Session { get; set; }

    public SessionWrapper( HttpSessionBase session )
    {
        Session = session;
    }

    public SessionWrapper() : this( HttpContext.Current.Session ) { }

    public string Key
    {
         get { return Session["key"] as string ?? "none";
    }

    public int MaxAllowed
    {
         get { return Session["maxAllowed"] as int? ?? 10 }
    }
}

Используется в качестве

 var session = new SessionWrapper(Session);

 string key = session.Key;
 int maxAllowed = session.maxAllowed;
tvanfosson
источник
3

создать вспомогательную функцию

public static String GetValue( string key, string default )
{
    if ( Session[ key ] == null ) { return default; }
    return Session[ key ].toString();
}


string y = GetValue( 'key', 'none' );
scibuff
источник
2

Ответ Скита самый лучший - в частности, я думаю, что он ToStringOrNull()довольно элегантен и лучше всего соответствует вашим потребностям. Я хотел добавить в список методов расширения еще одну опцию:

Вернуть исходный объект или строковое значение по умолчанию для null :

// Method:
public static object OrNullAsString(this object input, string defaultValue)
{
    if (defaultValue == null)
        throw new ArgumentNullException("defaultValue");
    return input == null ? defaultValue : input;
}

// Example:
var y = Session["key"].OrNullAsString("defaultValue");

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

one.beat.consumer
источник
Зачем генерировать исключение, null defaultValueесли оно не нужно (то есть input != null)?
Аттила,
input != nullEval возвращает объект , как он сам. input == nullвозвращает строку, указанную в качестве параметра. поэтому возможно, что кто-то может позвонить .OnNullAsString(null)- но цель (хотя и редко полезный метод расширения) заключалась в том, чтобы убедиться, что вы либо получите объект обратно, либо строку по умолчанию ... никогда не null
one.beat.consumer
input!=nullСценарий будет возвращать только вход , если defaultValue!=nullимеет место; в противном случае он выбросит ArgumentNullException.
Аттила
0

Это мой маленький типобезопасный "оператор Элвиса" для версий .NET, которые не поддерживают ?.

public class IsNull
{
    public static O Substitute<I,O>(I obj, Func<I,O> fn, O nullValue=default(O))
    {
        if (obj == null)
            return nullValue;
        else
            return fn(obj);
    }
}

Первый аргумент - это проверяемый объект. Во-вторых, функция. И третье - нулевое значение. Итак, для вашего случая:

IsNull.Substitute(Session["key"],s=>s.ToString(),"none");

Это также очень полезно для типов, допускающих значение NULL. Например:

decimal? v;
...
IsNull.Substitute(v,v.Value,0);
....
Томаз Стих
источник