Wish there is no NULL

I think I spent a good amount of time in my developer career to write code that guard against null or fix a problem related to null reference exception. I believe every developer’s story is same here. Here is a quotation by Tony Hoare on null.

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years

That’s why its a good practice to avoid NULL whenever possible. For example if you return a collection of data from some function never return null just return Enumerable.Empty instead. Still it doesn’t solve all your null related problems. You most probably using a library that you didn’t write and that might return null in some situations. As it is clear that we like or not, NULL is fact and we need to handle this our day to day development process. Lets see how you can make your life easy just using some extension methods. I will discuss much better option known as “Maybe” (based on Haskell monad) end of this post.

public static class NullSafeExtensions
{
    public static IEnumerable<T> NullSafe<T>(this IEnumerable<T> source)
    {
        return source ?? Enumerable.Empty<T>();
    }

    public static string NullSafe(this string source)
    {
        return source ?? string.Empty;
    }
    
    public static TResult NullSafeResult<TInput, TResult>(this TInput input, Func<TInput, TResult> fetch)
    {
        if (input == null) return default(TResult);
        return fetch.Invoke(input);
    }

    public static void NullSafeAccess<TInput>(this TInput input, Action<TInput> action)
    {
        if(input == null) return;

        action.Invoke(input);
    }
}

Lets see some comparative view of handling null with and without using extensions methods. Overall using extension method remove the extra if/else and curly braces and makes the code much clean.

Guard against null when using an enumerable


/* Sample: Cast an enumerable to another object */
// handle null general way
if(products != null){
    return products.select(product => new 
    { 
        Title = product.Title, 
        PriceDesc = product.Price.ToString("C") 
    });
}
else{
    return Enumerable.Empty<Product>
}

// handle null using extension method
return products
        .NullSafe()
        .select(product => new 
        { 
            Title = product.Title, 
            PriceDesc = product.Price.ToString("C") 
        });

Guard against null when using string


/* Sample: modify string */
// handle null
name = (name ?? string.Empty).Trim();

// handle null with extension method
name = name.NullSafe().Trim();

Guard against null when read property of an object

/* Sample: read property from object */
// handle null
string name = string.Empty;
if (person != null)
{
    name = person.Name;
}

// handle null without extension method
var name = person
    .NullSafeResult(x => x.Name)
    .NullSafe();

Guard against null when accessing/modifying a property of an object


/* Sample: Handle null on modifying property of object */
// handle null
if (person != null)
{
    person.Name = (person.Name ?? string.Empty).Trim();
}

// handle null with extension method
person
    .NullSafeAccess(x => x.Name = x.Name.NullSafe().Trim());


Now lets see another way of handling null in your system. Lets create a class “Maybe” as below.


public class Maybe<T>
{
    public static readonly Maybe<T> None = new Maybe<T>();

    private readonly T _value;
    private readonly bool _hasValue;

    private Maybe()
    {
        _value = default (T);
        _hasValue = false;
    }

    public Maybe(T value)
    {
        _value = value;
        _hasValue = _value != null 
                    && !_value.Equals(default (T));
    }

    public T Value { get { return _value; } }

    public bool HasValue
    {
        get { return _hasValue; }
    }
}

Now write some extension methods for Maybe class so that we can use this fluently in our system.


public static class MaybeExtensions
{
    public static Maybe<TInput> ToMaybe<TInput>(this TInput input)
    {
        return new Maybe<TInput>(input);
    }

    public static Maybe<TResult> Get<TInput, TResult>(this Maybe<TInput> source, Func<TInput, TResult> func)
    {
        return source.HasValue 
            ? new Maybe<TResult>(func.Invoke(source.Value)) 
            : Maybe<TResult>.None;
    }

    public static Maybe<TInput> Do<TInput>(this Maybe<TInput> source, Action<TInput> action)
    {
        if (source.HasValue)
        {
            action.Invoke(source.Value);
        }

        return source;
    } 

    public static Maybe<TInput> If<TInput>(this Maybe<TInput> source, Func<TInput,bool> func)
    {
        return source.HasValue && func.Invoke(source.Value) ? source : Maybe<TInput>.None;
    }

    public static Maybe<TInput> IfNot<TInput>(this Maybe<TInput> source, Func<TInput, bool> func)
    {
        return source.HasValue && !func.Invoke(source.Value) ? source : Maybe<TInput>.None;
    }

    public static Maybe<TInput> If<TInput>(this Maybe<TInput> source, bool condition)
    {
        return source.HasValue && condition ? source : Maybe<TInput>.None;
    }

    public static Maybe<TInput> IfNot<TInput>(this Maybe<TInput> source, bool condition)
    {
        return source.HasValue && !condition ? source : Maybe<TInput>.None;
    }

    public static TResult Return<TInput, TResult>(this Maybe<TInput> source, Func<TInput,TResult> fetch, TResult defaultValue)
    {
        return source.HasValue
            ? fetch.Invoke(source.Value)
            : defaultValue;
    }

    public static TResult Return<TInput, TResult>(this Maybe<TInput> source, Func<TInput, TResult> fetch)
    {
        return source.HasValue
            ? fetch.Invoke(source.Value)
            : default(TResult);
    }
}

Now lets see how we can use “Maybe” to eliminate null reference problem


decimal? nullPrice = null;
nullPrice.ToMaybe().Get(x => x * 5).Value.ShouldBeNull();

decimal? price = 5.0m;
price.ToMaybe().Get(x => x * 5).Value.ShouldEqual(25);

Person nullPerson = null;

nullPerson.ToMaybe()
    .Get(x => x.Address)
    .Return(addres => addres.Street).ShouldBeNull();


nullPerson.ToMaybe()
    .Get(x => x.Address)
    .Return(addres => addres.Street, "EmptyStreet").ShouldEqual("EmptyStreet");

var person = new Person
{
    Name = "ruhul",
    Address = new Address
    {
        Street = "West nakhalpara"
    }
};

person.ToMaybe()
    .Get(x => x.Address)
    .Return(address => address.Street).ShouldEqual("West nakhalpara");

person.Address = null;

person.ToMaybe()
    .Get(x => x.Address)
    .Return(address => address.Street).ShouldBeNull();


person.Address = new Address { Street = "Nakhalpara" };
person.ToMaybe()
    .Get(p => p.Address)
    .If(address => string.IsNullOrWhiteSpace(address.Country))
    .Do(address => address.Country = "Bangladesh")
    .Return(address => address.Country).ShouldEqual("Bangladesh");

person.Address.Country.ShouldEqual("Bangladesh");

List<Person> persons = null;

persons.ToMaybe().Get(x => x.Select(y => new{ Title = y.Name })).Value.ShouldBeNull();


The concept of Maybe Monad is nothing new. You can find lots of example and different variations of Maybe implementation. Just use the one you like and I believe your will find the benefit of this. Its a very powerful technique which can make your code very fluent and clean. That’s all for today. Happy coding šŸ™‚

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s