122

I love the null-coalescing operator because it makes it easy to assign a default value for nullable types.

 int y = x ?? -1;

That's great, except if I need to do something simple with x. For instance, if I want to check Session, then I usually end up having to write something more verbose.

I wish I could do this:

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

But you can't because the .ToString() gets called before the null check so it fails if Session["key"] is null. I end up doing this:

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

It works and is better, in my opinion, than the three-line alternative:

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

Even though that works I am still curious if there is a better way. It seems no matter what I always have to reference Session["key"] twice; once for the check, and again for the assignment. Any ideas?

CatDadCode
  • 58,507
  • 61
  • 212
  • 318
  • 20
    This is when I wish C# had a "safe navigation operator" (`.?`) like [Groovy has](http://groovy.codehaus.org/Operators#Operators-SafeNavigationOperator). – Cameron Mar 20 '12 at 16:11
  • 2
    @Cameron: This is when I wish C# could treat nullable types (including reference types) as a monad, so you wouldn’t need a “safe navigation operator”. – Jon Purdy Mar 21 '12 at 02:33
  • 3
    The inventor of null references called it his "billion dollar mistake" and I tend to agree. See http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare – Jamie Ide Mar 21 '12 at 13:57
  • His actual mistake is the unsafe (not language-enforced) mixing of nullable and non-nullabel types. – MSalters Mar 22 '12 at 12:50

10 Answers10

182

What about

string y = (Session["key"] ?? "none").ToString();
BlackBear
  • 22,411
  • 10
  • 48
  • 86
  • 2
    @Matthew: No because Session values are of type Object – BlackBear Mar 20 '12 at 14:21
  • 1
    @BlackBear but the value returned is most probably a string, so the cast is valid – Firo Mar 20 '12 at 14:29
  • This was the most direct answer to my question so I'm marking the answer, but Jon Skeet's extension method `.ToStringOrDefault()` is my preferred way of doing it. However, I am using this answer within Jon's extension method ;) – CatDadCode Mar 20 '12 at 14:33
  • 10
    I dislike this because if you have any other type of object stuffed in the session than what you expect you may be hiding some subtle bugs in your program. I'd rather use a safe cast because I think it's likely to surface errors faster. It also avoids calling ToString() on a string object. – tvanfosson Mar 20 '12 at 19:17
  • @tvanfosson: I'm not sure I understand. Can you provide an example? – BlackBear Mar 20 '12 at 19:28
  • `Session["key"] = 10; string y = (Session["key"] ?? "none").ToString();` y becomes the string "10" when arguably it should either throw an exception or get the default value, probably the former. At least you should be able to tell the difference between that and `Session["key"] = "10"; string y = (Session["key"] ?? "none").ToString();` – tvanfosson Mar 20 '12 at 20:21
  • @tvanfosson: I get your point but you should talk with the OP about this. Skeet's answer behaves just like mine by the way. – BlackBear Mar 20 '12 at 20:31
  • @tvanfosson: I assumed this was exactly the desired behaviour. *If* the value in the session is expected to be a string already, then casting (followed by the null-coalescing operator) would clearly be a preferable approach. – Jon Skeet Mar 21 '12 at 06:43
  • @JonSkeet - hmm, I can see where you get that but if that's the case I'd prefer to leave it as the nullable type and take care of the conversion to a default string when it gets rendered (if the string is fixed, i.e., not from a configuration or localized). Maybe that's what's going on, but I'd also never use the session directly at that point either, but stuff it into a model property. – tvanfosson Mar 21 '12 at 12:14
  • To be clear, in the case where the default is part of the "business" of the application, I think it's even more important to go with a wrapper around the session to consistently enforce the business rules that go along with it - using the appropriate conversion technique or extension method internally. – tvanfosson Mar 21 '12 at 12:25
130

If you're frequently doing this specifically with ToString() then you could write an extension method:

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

...

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

Or a method taking a default, of course:

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

...

string y = Session["key"].ToStringOrDefault("none");
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • StackExchange's DataExplorer has an extension method similar to this with the added benefit of multiple, default values. `string IsNullOrEmptyReturn(this string s, params string[] otherPossibleResults)` http://code.google.com/p/stack-exchange-data-explorer/source/browse/App/StackExchange.DataExplorer/ExtensionMethods.cs#84 – David Murdoch Mar 20 '12 at 17:20
  • 7
    I cannot agree with this at all. Extension methods on `object` are a curse and junk up a code base, and extension methods that operate without error on null `this` values are pure evil. – Nick Larsen Mar 20 '12 at 19:01
  • 10
    @NickLarsen: Everything in moderation, I say. Extension methods which work with null can be very useful, IMO - so long as they're *clear* about what they do. – Jon Skeet Mar 20 '12 at 19:03
21

You could also use as, which yields null if the conversion fails:

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

This would return "none" even if someone stuffed an int in Session["key"].

CatDadCode
  • 58,507
  • 61
  • 212
  • 318
Andomar
  • 232,371
  • 49
  • 380
  • 404
  • 1
    This only works when you wouldn't need `ToString()` in the first place. – Abel Mar 21 '12 at 21:04
  • 1
    I’m surprised nobody has downvoted this answer yet. This is semantically completely different from what the OP wants to do. – Timwi Mar 22 '12 at 15:27
  • @Timwi: The OP uses `ToString()` to convert an object containing a string to a string. You can do the same with `obj as string` or `(string)obj`. It's a fairly common situation in ASP.NET. – Andomar Mar 22 '12 at 16:13
  • 5
    @Andomar: No, the OP is calling `ToString()` on an object (namely, `Session["key"]`) whose type he didn’t mention. It could be any kind of object, not necessarily a string. – Timwi Mar 22 '12 at 16:50
13

If it will always be a string, you can cast:

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

This has the advantage of complaining instead of hiding the mistake if someone stuffs an int or something in Session["key"]. ;)

Ry-
  • 218,210
  • 55
  • 464
  • 476
10

All of the suggested solutions are good, and answer the question; so this is just to extend on it slightly. Currently the majority of answers only deal with null validation and string types. You could extend the StateBag object to include a generic GetValueOrDefault method, similar to the answer posted by Jon Skeet.

A simple generic extension method that accepts a string as a key, and then type checks the session object. If the object is null or not the same type, the default is returned, otherwise the session value is returned strongly typed.

Something like this

/// <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;
}
Richard
  • 8,110
  • 3
  • 36
  • 59
  • 1
    Can you include a usage sample for this extension method? Doesn't StateBag deal with view state and not session? I'm using ASP.NET MVC 3 so I don't really have simple access to view state. I think you want to extend `HttpSessionState`. – CatDadCode Mar 20 '12 at 19:32
  • this answer requires retrieving the value 3x and 2 casts if it succeeds. (i know it's a dictionary, but beginners may use similar practices on expensive methods.) – Jake Berger Mar 20 '12 at 20:37
  • 3
    `T value = source[key] as T; return value ?? defaultValue;` – Jake Berger Mar 20 '12 at 20:38
  • 1
    @jberger Casting to value using "as" is inaccessible as there isn't a class constraint on the generic type as potentially you may want to return a value such as bool. @AlexFord My apologies, you would want to extend the `HttpSessionState` for the session. :) – Richard Mar 21 '12 at 09:59
  • indeed. as richard noted, requires the constraint. (... and another method if you want to use value types) – Jake Berger Mar 21 '12 at 14:29
7

We use a method called NullOr.

Usage

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

Source

/// <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
  • 65,159
  • 33
  • 165
  • 230
  • Yes, this is the more generic answer to the problem intended - you beat me to it - and a candidate for safe navigation (if you don't mind the lambda-s for simple things) - but it's still a bit cumbersome to write, well :). Personally I'd always pick the ? : instead (if not expensive, if it is then rearrange anyway)... – NSGaga-mostly-inactive Mar 26 '12 at 12:18
  • ...And the 'Naming' is the real problem with this one - nothing really seems to depict right (or 'adds' too much), or is long - NullOr is good but too much emphasis on 'null' IMO (plus you have ?? still) - 'Property', or 'Safe' is what I used. value.Dot(o=>o.property) ?? @default maybe? – NSGaga-mostly-inactive Mar 26 '12 at 12:26
  • @NSGaga: We went back and forth on the name for quite some time. We did consider `Dot` but found it too undescriptive. We settled for `NullOr` as a good tradeoff between self-explanation and brevity. If you really didn’t care about the naming at all, you could always call it `_`. If you find the lambdas too cumbersome to write, you can use a snippet for this, but personally I find it quite easy enough. As for `? :`, you can’t use that with more complex expressions, you’d have to move them to a new local; `NullOr` allows you to avoid that. – Timwi Mar 26 '12 at 14:23
6

My preference, for a one off, would be to use a safe cast to string in case the object stored with the key isn't one. Using ToString() may not have the results you want.

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

As @Jon Skeet says, if you find yourself doing this a lot an extension method or, better, yet maybe an extension method in conjunction with a strongly typed SessionWrapper class. Even without the extension method, the strongly typed wrapper might be a good idea.

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 }
    }
}

Used as

 var session = new SessionWrapper(Session);

 string key = session.Key;
 int maxAllowed = session.maxAllowed;
tvanfosson
  • 524,688
  • 99
  • 697
  • 795
3

create an auxiliary function

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


string y = GetValue( 'key', 'none' );
scibuff
  • 13,377
  • 2
  • 27
  • 30
2

Skeet's answer is the best - in particularly I think his ToStringOrNull() is quite elegant and suits your need best. I wanted to add one more option to the list of extension methods:

Return original object or default string value for 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");

Use var for the returned value as it will come back as the original input's type, only as the default string when null

one.beat.consumer
  • 9,414
  • 11
  • 55
  • 98
  • Why throw an exception on `null` `defaultValue` if it is not needed (that is `input != null`)? – Attila Mar 21 '12 at 17:00
  • An `input != null` eval will return the object as itself. `input == null` returns a the string provided as a param. therefore it is possible a person could call `.OnNullAsString(null)` - but the purpose (albeit rarely useful extension method) was to ensure you either get the object back or default string... never null – one.beat.consumer Mar 21 '12 at 17:17
  • The `input!=null` scenario will only return the input if `defaultValue!=null` also holds; otherwise it will throw an `ArgumentNullException`. – Attila Mar 21 '12 at 17:59
0

This is my little type safe "Elvis operator" for versions of .NET that do not support ?.

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

First argument is the tested object. Second is the function. And third is the null value. So for your case:

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

It is very useful for nullable types too. For example:

decimal? v;
...
IsNull.Substitute(v,v.Value,0);
....
Tomaz Stih
  • 529
  • 3
  • 10