Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extending the C# Coalesce Operator

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");
like image 248
laktak Avatar asked Mar 20 '09 10:03

laktak


People also ask

Can you extend the C drive?

To expand system partition (C: driver) size Before expanding C drive size, need to confirm there is unallocated space next to C drive. Right click on C drive then select “Extend volume”, then follow the onscreen instruction to finish the process.

How do I extend C drive greyed out?

As here is no unallocated space after the C partition drive, so extend volume greyed out. You need to have an “unallocated disk space” to the right of the Partition\Volume you wish to extend on the same drive. Only when “unallocated disk space” is available “extend” option is highlighted or available.


2 Answers

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 :)

Update

C# 6 has such an operator - the null conditional operator, ?. For example:

var street = customer?.PrimaryAddress?.Street;

Use it in conjunction with the original null-coalescing operator if you still want a default. For example:

var street = customer?.PrimaryAddress?.Street ?? "(no address given)";

Or, based on the original code in the question:

Console.WriteLine(getSomeFoo()?.Value ?? "default"); 

Noting, of course, that providing a default this way works only if it's okay to use that default value even when the final property value is available but set to null for some reason.

The result of an expression x?.y is null if x evaluates to null; otherwise it's the result of x.y. Oh, and you can use it for conditional method invocation, too:

possiblyNull?.SomeMethod();
like image 189
Jon Skeet Avatar answered Sep 21 '22 12:09

Jon Skeet


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.

like image 26
Marc Gravell Avatar answered Sep 21 '22 12:09

Marc Gravell