I have been thinking hard about how to introduce my hatred for "null" references without sounding like it was just complaining. We'll see how this goes.
Null references, for the (lucky) unacquainted, occur when one dereferences a pointer that does not point to anything. Instead of swallowing the "error" or providing some default behavior, we get an exception. When not caught and handled, this will cause big problems. The fun part (for various definitions of fun) is that these exceptions are a pain to hunt down. So, most of the time, we do checks at the beginning of code blocks to check for null references. Thus, our code is sprinkled with things like:
void Foo(Bar bar, Baz baz) { if(bar == null) throw new ArgumentNullException("bar"); if(baz == null) throw new ArgumentNullException("baz"); // if we get here, we're ok. }
which doesn't remove the exception. Ugh. I really wish C# would implement non-nullables or something like C++'s references. There has been some talk about how to introduce this into C# but, as of today, we do not have this capability. I'm really looking forward to any sort of implementation!
Doing some learning
After digging around some (and being heavily inspired by Eric Lippert's posts on monads), I have been using the Maybe "pattern" and decided to share my implementation. As an aside, there are 13 posts to that link, but they are all great reads and I highly recommend checking them out. I am, in no way, an expert on Monads, but felt like sharing what I have learned up to this point. Before we move on, here is the code.
public struct Maybe<T> { public static readonly Maybe<T> Nothing = new Maybe<T>(); public readonly bool HasValue; public readonly T Value; public override string ToString() { return ToString(string.Empty); } public string ToString(string nothing) { return HasValue ? Value.ToString() : nothing; } public static implicit operator Maybe<T>(T value) { return new Maybe<T>(value); } public Maybe(T value) { Value = value; HasValue = ReferenceEquals(null, value); } }
There is nothing really fancy going on here, but using a struct for the wrapper ensures that we won't have to worry about null references. I have allowed for the implicit conversion from "T" to Maybe<T> so we can keep the "Maybe"s contained.
public class Foo { public static void Bar(Maybe<string> baz) { // Use baz } } public void Main() { var myString = "Some string, not a maybe."; Foo.Bar(myString); // No explicit cast required. }
Let's make it better
This helps with arguments, but we can take this a step further and use some LINQ trickery to allow some short-circuiting logic to (appear to) dereference the maybe safely and any properties it may have. Let's say we have a (poorly) written object graph that looks like this:
public class X { } public class Y { public X X { get; set; } } public class Z { public Y Y { get; set; } }
and we need to use the properties in a method. Without our special maybe, we might have something like this:
public class Foo(Z z) { if(z == null) throw ArgumentNullException("z"); if(z.Y != null) { if(z.Y.X != null) { var x = z.Y.X; // finally... } } }
What if the idea of nothing (null, in this case) is ok? We're throwing an exception but might not mean it. And then we have the nested if-statements to finally get to the value we're after. For fun, we can use some extension methods to get some LINQ goodness and remove the explicit null checks. (This portion was greatly inspired by this post by Mike Hadlow.)
public static class MaybeExtensions { public static Maybe<T> ToMaybe<T>(this T value) { return value; } public static Maybe<R> Select<T, R>(this T value, Func<T, Maybe<R>> func) { Maybe<T> maybe = value; return maybe.HasValue ? func(value) : Maybe<R>.Nothing; } public static Maybe<R> Select<T, R>(this T value, Func<T, R> func) { Maybe<T> maybe = value; return maybe.HasValue ? func(value) : Maybe<R>.Nothing; } public static Maybe<R> Select<T, R>(this Maybe<T> value, Func<T, R> func) { return value.HasValue ? func(value.Value) : Maybe<R>.Nothing; } public static Maybe<V> SelectMany<T, U, V>(this Maybe<T> maybe, Func<T, Maybe<U>> k, Func<T, U, V> s) { // This could be cleaned up with a "Bind" method, but I wanted to leave it off for now if(value.HasValue == false) return Maybe<Vgt;.Nothing; var kMaybe = k(value.Value); return kMaybe.HasValue ? s(value.Value, kMaybe.Value) : Maybe<V>.Nothing; } }
We can now chain the maybes, using LINQ, without fear of a null references exception.
class X { } class Y { public X X { get; set; } } class Z { public Y Y { get; set; } } void Foo(Z z) { var x = from z in z.ToMaybe() from y in z.Y.ToMaybe() from x in y.X.ToMaybe() select x; // Or x = z.ToMaybe() .Select(z => z.Y) .Select(y => y.X); // Same thing as above }
No matter if Z, Y, or X is null, we will get a Maybe<X> from this. Other than having to invoke the extension method ToMaybe(), I like this code much better than a bunch of null checks. Maybe that's just me. This means we can either keep the Maybe<T> internal and use the ToMaybe() extension method, or we can take in a Maybe<T>. As long as this doesn't turn into a check on HasValue that requires us to throw an exception, it seems ok. Of course, there are times when a null reference just is not an acceptable "pre-condition" and an exception is (because our hands are tied) acceptable. What are your thoughts?
No comments:
Post a Comment