Який правильний спосіб перевірити наявність нульових значень?


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 # міг ставитись до змінних типів (включаючи еталонні типи) як монаду, тому вам не потрібен "безпечний навігаційний оператор".
Джон Перді,

3
Винахідник нульових посилань назвав це "помилкою в мільярд доларів", і я схильний погодитися. Дивіться infoq.com/presentations/…
Jamie Ide

Його фактична помилка - небезпечне (не примусове до мови) змішування типів з нульовим та ненульовим позначенням.
MSalters

@JamieIde Дякую за дуже цікаве посилання. :)
BobRodes

Відповіді:


182

А як на рахунок

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

79
Сила сильна при цьому.
Шев

2
@Matthew: Ні, тому що значення сеансу мають тип "Об'єкт"
BlackBear

1
@BlackBear, але значення, що повертається, швидше за все, є рядком, тому акторський склад є дійсним
Firo

Це було найбільш прямою відповіддю на моє запитання, тому я відзначаю відповідь, але спосіб розширення Джона Скіта - .ToStringOrDefault()це мій бажаний спосіб зробити це. Однак я використовую цю відповідь у методі розширення Джона;)
Шев

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()простий і елегантний. Приємне рішення.
Шев

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()в першу чергу.
Авель

1
Я здивований, що ніхто ще не відповів на цю відповідь. Це семантично абсолютно відрізняється від того, що хоче зробити ОП.
Тімві

@Timwi: ОП використовує ToString()для перетворення об'єкта, що містить рядок, у рядок. Можна зробити те ж саме з obj as stringабо (string)obj. Це досить поширена ситуація в ASP.NET.
Андомар

5
@Andomar: Ні, ОП викликає ToString()об'єкт (а саме Session["key"]), тип якого він не згадував. Це може бути будь-який об’єкт, не обов'язково рядок.
Тімві

13

Якщо це завжди буде a string, ви можете подати:

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

Це має перевагу скаржитися, а не приховувати помилку, якщо хтось набиває intщось чи щось Session["key"]. ;)


10

Усі запропоновані рішення хороші та відповідають на питання; так що це просто трохи подовжитись на ньому. В даний час більшість відповідей стосуються лише нульової перевірки та типів рядків. Ви можете розширити StateBagоб'єкт, щоб він включав загальний GetValueOrDefaultметод, подібний до відповіді, яку опублікував Джон Скіт.

Простий загальний метод розширення, який приймає рядок як ключ, а потім тип перевіряє об'єкт сеансу. Якщо об'єкт є недійсним або не є одним і тим же типом, повертається за замовчуванням, інакше значення сеансу повертається сильно набраним.

Щось на зразок цього

/// <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.
Шев

ця відповідь вимагає отримати значення 3x та 2 касти, якщо це успішно. (я знаю, що це словник, але початківці можуть використовувати подібні практики на дорогих методах.)
Джейк Бергер

3
T value = source[key] as T; return value ?? defaultValue;
Джейк Бергер

1
@jberger Кастинг до значення, використовуючи "як", недоступний, оскільки не існує обмеження класу для загального типу, оскільки потенційно ви можете повернути таке значення, як 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 хороший, але занадто великий акцент на "нульовому" IMO (плюс ви все ще) - "Властивість" або "Безпечно" - це те, що я використовував. value.Dot (o => o.property) ?? @ за замовчуванням, можливо?
NSGaga - здебільшого неактивний

@NSGaga: Ми досить довго переходили по імені. Ми вважали, Dotале вважали це занадто неописовим. Ми вирішили це NullOrяк хороший компроміс між самоясненням і стислістю. Якщо ви насправді взагалі не переймалися називанням, ви завжди можете його зателефонувати _. Якщо ви вважаєте лямбда занадто громіздкою для написання, ви можете використовувати для цього фрагмент, але особисто я вважаю це досить просто. Щодо того ? :, ви не можете використовувати це зі складнішими виразами, вам доведеться перемістити їх до нового локального; NullOrдозволяє уникнути цього.
Тімві

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)- але метою (хоч і рідко корисним методом розширення) було забезпечити повернення об'єкта або рядок за замовчуванням ... ніколи не нульове
one.beat.consumer

input!=nullСценарій буде повертати тільки вхід , якщо defaultValue!=nullмає місце; інакше це кине ArgumentNullException.
Аттіла

0

Це мій маленький безпечний "оператор Elvis" для версій .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");

Це дуже корисно і для змінних типів. Наприклад:

decimal? v;
...
IsNull.Substitute(v,v.Value,0);
....
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.