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


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"].


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"]. ;)


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();
    }
}

Да, это более общий ответ на задуманную проблему - вы меня опередили - и кандидат на безопасную навигацию (если вы не возражаете против лямбда-выражений для простых вещей) - но писать его все же немного громоздко, хорошо :). Лично я всегда выбираю? : вместо этого (если не дорого, то все равно переставить) ...
— 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;

3

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

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


string y = GetValue( 'key', 'none' );

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


Зачем генерировать исключение, 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);
....
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.