views:

855

answers:

7

Before I explain what I want to do, if you look at the following code, would you understand what it's supposed to do? (updated - see below)

Console.WriteLine(
  Coalesce.UntilNull(getSomeFoo(), f => f.Value) ?? "default value");

C# already has a null-coalescing operator that works quite well on simple objects but doesn't help if you need to access a member of that object.

E.g.

Console.WriteLine(getSomeString()??"default");

works very well but it won't help you here:

public class Foo
{
  public Foo(string value) { Value=value; }
  public string Value { get; private set; }
}

// this will obviously fail if null was returned
Console.WriteLine(getSomeFoo().Value??"default"); 

// this was the intention
Foo foo=getSomeFoo();
Console.WriteLine(foo!=null?foo.Value:"default");

Since this is something that I come across quite often I thought about using an extension method (old version):

public static class Extension
{
  public static TResult Coalesce<T, TResult>(this T obj, Func<T, TResult> func, TResult defaultValue)
  {
    if (obj!=null) return func(obj);
    else return defaultValue;
  }

  public static TResult Coalesce<T, TResult>(this T obj, Func<T, TResult> func, Func<TResult> defaultFunc)
  {
    if (obj!=null) return func(obj);
    else return defaultFunc();
  }
}

Which allows me to write:

Console.WriteLine(getSomeFoo().Coalesce(f => f.Value, "default value"));

So would you consider this code to be readable? Is Coalesce a good name?

Edit 1: removed the brackets as suggested by Marc

Update

I really liked lassevk's suggestions and Groo's feedback. So I added overloads and didn't implement it as an extension method. I also decided that defaultValue was redundant because you could just use the existing ?? operator for that.

This is the revised class:

public static class Coalesce
{
  public static TResult UntilNull<T, TResult>(T obj, Func<T, TResult> func) where TResult : class
  {
    if (obj!=null) return func(obj);
    else return null;
  }

  public static TResult UntilNull<T1, T2, TResult>(T1 obj, Func<T1, T2> func1, Func<T2, TResult> func2) where TResult : class
  {
    if (obj!=null) return UntilNull(func1(obj), func2);
    else return null;
  }

  public static TResult UntilNull<T1, T2, T3, TResult>(T1 obj, Func<T1, T2> func1, Func<T2, T3> func2, Func<T3, TResult> func3) where TResult : class
  {
    if (obj!=null) return UntilNull(func1(obj), func2, func3);
    else return null;
  }

  public static TResult UntilNull<T1, T2, T3, T4, TResult>(T1 obj, Func<T1, T2> func1, Func<T2, T3> func2, Func<T3, T4> func3, Func<T4, TResult> func4) where TResult : class
  {
    if (obj!=null) return UntilNull(func1(obj), func2, func3, func4);
    else return null;
  }
}

Sample usage:

Console.WriteLine(
  Coalesce.UntilNull(getSomeFoo(), f => f.Value) ?? "default value");

Another sample:

public class Bar
{
  public Bar Child { get; set; }
  public Foo Foo { get; set; }
}

Bar bar=new Bar { Child=new Bar { Foo=new Foo("value") } };

// prints "value":
Console.WriteLine(
  Coalesce.UntilNull(bar, b => b.Child, b => b.Foo, f => f.Value) ?? "null");

// prints "null":
Console.WriteLine(
  Coalesce.UntilNull(bar, b => b.Foo, f => f.Value) ?? "null");
+3  A: 

Yes, I would understand it. Yes, coalesce is a good name. Yes, it would be better if C# had a null-safe dereferencing operator like Groovy and some other languages :)

Jon Skeet
+3  A: 

It confused me already... normally, you think of coalesce acting on its values - I imagined that the first non-null of (f) => f.Value, and "default value" would be returned, which isn't the case (the null test is on the originating instance).

Note it would be clearer without the brackets?

f => f.Value

What you are actually doing is similar to Select - so something like SafeSelect would be a good name, IMO (but maybe not exactly that...).

Or even simply Dereference, as long as the argument name (etc) makes it clear what the second arg is for.

Marc Gravell
Thanks - removed the brackets. I think SafeSelect would suggest an enumeration which it isn't ... on Dereference I'm not sure ... the hardest part is always naming things :)
chris
I have to agree with Marc, your extension method does not act like the ?? operator in your code sample.
Samuel
A: 

Seems readable enough, though it's still a bit clumsy.

This seems like a perfect opportunity to implement the null object pattern though.

Consider:

public class Foo
{
  public Foo(string value) { Value=value; }
  public string Value { get; private set; }
  private static Foo nullFoo = new Foo("default value");
  public static Foo NullFoo { get { return nullFoo; } }
}

Then have getSomeFoo() return Foo.NullFoo instead of a null. It requires a little extra thought, but generally makes for nicer code.

Update in response to comments:

Let's say you don't control Foo, you can still (often) do this (which is more how you'd want to implement it regardless):

public class NullFoo : Foo
{
    private NullFoo() : base("default value") { }
    private static NullFoo instance = new NullFoo();
    public static Foo Instance { get { return instance; } }
}

Then return NullFoo.Instance from getSomeFoo(). If you don't control getSomeFoo() either, you yet still have the option to do this:

Console.WriteLine((getSomeFoo() ?? NullFoo.Instance).Value);
Wedge
That forces immutability, though (at the minimum, popsicle immutability) - and doesn't play nicely with "default(T)" etc. Plus it more generally assumes that you control the type in question (which isn't always the case)
Marc Gravell
+1  A: 

If you use it often in a code base, I think it is good as it is not too hard to understand on first reading and reduced the size of the code – so helps me to see the wood from the trees.

However if is only used 1 or 2 times, I think an "in line" if will be better, as I don’t see to think about the meaning of a “if” the first time I see it.

By "in line" if - I mean a normal if statement that has not be hidden in a seperate method.

Ian Ringrose
Indeed; and there are scenarios where only the "in line if" will work - such as when constructing a lambda for LINQ-to-[some database]
Marc Gravell
by "in line if" you mean the ?: operator?
chris
Sorry I just mean a noraml "if" statement that has not be hidden inside of a seperate method
Ian Ringrose
+1  A: 

This could easily be extended as well:

public static TResult Coalesce<T, TResult>(this T obj, Func<T, TResult> func, TResult defaultValue)
{
    if (obj == null)
        return defaultValue;

    return func(obj);
}

public static TResult Coalesce<T1, T2, TResult>(this T1 obj, Func<T1, T2> func1, Func<T2, TResult> func2, TResult defaultValue)
{
    if (obj == null)
        return defaultValue;

    T2 obj2 = func1(obj);
    if (obj2 == null)
        return defaultValue;

    return func2(obj2);
}

public static TResult Coalesce<T1, T2, T3, TResult>(this T1 obj, Func<T1, T2> func1, Func<T2, T3> func2, Func<T3, TResult> func3, TResult defaultValue)
{
    if (obj == null)
        return defaultValue;

    T2 obj2 = func1(obj);
    if (obj2 == null)
        return defaultValue;

    T3 obj3 = func2(obj2);
    if (obj3 == null)
        return defaultValue;

    return func3(obj3);
}

public static TResult Coalesce<T1, T2, T3, T4, TResult>(this T1 obj, Func<T1, T2> func1, Func<T2, T3> func2, Func<T3, T4> func3, Func<T4, TResult> func4, TResult defaultValue)
{
    if (obj == null)
        return defaultValue;

    T2 obj2 = func1(obj);
    if (obj2 == null)
        return defaultValue;

    T3 obj3 = func2(obj2);
    if (obj3 == null)
        return defaultValue;

    T4 obj4 = func3(obj3);
    if (obj4 == null)
        return defaultValue;

    return func4(obj4);
}

Which could be used like this:

BinaryTreeNode node = LocateNode(someKey);
BinaryTreeNode grandFatherNode = node.Coalesce(n1 => n1.Parent, n2 => n2.Parent, null);

Which would replace:

BinaryTreeNode grandFatherNode = node.Parent.Parent; // or null if none
Lasse V. Karlsen
A: 

Why not write down normal coalesce function, then you could use it like this:

coalesce(something, something_else, "default");

In other words -- what do you need lambdas for?

macias
He needs lambdas because `something_else` is a property of `something`. Wrapping the property dereference in a lambda allows him to delay evaluating it until he knows it is safe to do so.
Sean Devlin
Wrong approach then, because you push knowledge of that to primitive party which should be black box. If you want lazy eval just wrap your property with lazy eval class and pass such mix to the method (coalesce here).Otherwise you would end up with coalesce, coalesce_lazy, etc.
macias
A: 

Hi Chris,

Great post! I updated your code to support returning Nullable because Nullable is implemented as a struct.

    public static class Coalesce
    {
        public static TResult UntilNull<T, TResult>(T obj, Func<T, TResult> func) where TResult : class
        {
            if (obj != null) return func(obj);
            else return null;
        }

        public static TResult UntilNull<T1, T2, TResult>(T1 obj, Func<T1, T2> func1, Func<T2, TResult> func2) where TResult : class
        {
            if (obj != null) return UntilNull(func1(obj), func2);
            else return null;
        }

        public static TResult UntilNull<T1, T2, T3, TResult>(T1 obj, Func<T1, T2> func1, Func<T2, T3> func2, Func<T3, TResult> func3) where TResult : class
        {
            if (obj != null) return UntilNull(func1(obj), func2, func3);
            else return null;
        }

        public static TResult UntilNull<T1, T2, T3, T4, TResult>(T1 obj, Func<T1, T2> func1, Func<T2, T3> func2, Func<T3, T4> func3, Func<T4, TResult> func4) where TResult : class
        {
            if (obj != null) return UntilNull(func1(obj), func2, func3, func4);
            else return null;
        }

        public static Nullable<TResult> UntilNull<T, TResult>(T obj, Func<T, Nullable<TResult>> func) where TResult : struct
        {
            if (obj != null) return func(obj);
            else return new Nullable<TResult>();
        }

        public static Nullable<TResult> UntilNull<T1, T2, TResult>(T1 obj, Func<T1, T2> func1, Func<T2, Nullable<TResult>> func2) where TResult : struct
        {
            if (obj != null) return UntilNull(func1(obj), func2);
            else return new Nullable<TResult>();
        }

        public static Nullable<TResult> UntilNull<T1, T2, T3, TResult>(T1 obj, Func<T1, T2> func1, Func<T2, T3> func2, Func<T3, Nullable<TResult>> func3) where TResult : struct
        {
            if (obj != null) return UntilNull(func1(obj), func2, func3);
            else return new Nullable<TResult>();
        }

        public static Nullable<TResult> UntilNull<T1, T2, T3, T4, TResult>(T1 obj, Func<T1, T2> func1, Func<T2, T3> func2, Func<T3, T4> func3, Func<T4, Nullable<TResult>> func4) where TResult : struct
        {
            if (obj != null) return UntilNull(func1(obj), func2, func3, func4);
            else return new Nullable<TResult>();
        }
    }
James