简体   繁体   中英

Maybe monad using expression trees?

Ugly:

string city = null;
if (myOrder != null && myOrder.Customer != null)
  city = myOrder.Customer.City;

Better ( maybe monad ):

var city = myOrder
               .With(x => x.Customer)
               .With(x => x.City)

Even better? Any reason this couldn't be written?

var city = Maybe(() => myOrder.Customer.City);

Yes, it should be possible. However, it's quite a bit more complicated than it looks on the surface to implement an expression tree re-writer correctly. Especially if you want to be able to correctly handle fields, properties, indexed properties, method calls, and other constructs that are valid in arbitrary expressions.

It may also not be the most well-performing operation since to evaluate the expression you have to dynamically compile the expression tree into a lambda function each time.

There's an implementation on this pattern on CodePlex . I've never personally used it, so I can't say how well implemented it is, or whether it handles all of the cases I've described.

An alternative to creating an expression tree re-writer, is to write Maybe() to accept a lambda function (rather than an expression tree) and catch any ArgumentNullException thrown, returning default(T) in those cases. It rubs many people the wrong way to use exceptions for flow control in this way ... but it's certainly an easier implementation to get right. I personally avoid it myself since it can mask null reference errors within methods called as part of the expression, which is not desirable.

I recently implemented some monads in C# (including a basic expression tree parser inspired by a Bartosz Milewski article).

Take a look if you're interested: https://github.com/htoma/monads/blob/master/expressionMonad/expressionMonad/Program.cs

Some points that come to my mind:

  • .Solutions work fine for memory objects, but run into trouble with EF as these static calls cannot be converted to run against persistent storage (that is, SQL DB). This limits the scope of application somewhat heavily.

  • I will practically always want to know whether the chain did produce valid result. Hence, I will have one conditional block if(city == null) in any case.

  • Any current solution other than "the ugly" involves Expressions.

Hence, my choice would be something like

var property = ( () => myOrder.Customer.City );
city = HasValue(property) ? property.Invoke() : "unknown";

HasValue(Expression e) walks through the LINQ expression tree recursively until it either reaches end (returning true) or encounters null-valued property (returning false). The implementation should be simple, use MethodInfo Member of MemberExpression class to parse the AST. One could also implement getter this way as Brian suggested, but I like above better because HasValue always returns bool . Further:

  • Member invokations can be handled, too.
  • Evaluation could be made as myOrder.HasValue(x => x.Customer.City) but that brings some complications.

I know that my implementation of Maybe (as per CodeProject article) carries a cost, but I'm sure it's nothing compared to the idea of getting an Expression<T> involved there. Basically you're talking Reflection all the way. I wouldn't mind if it was pre-compiled, Roslyn-style, but we aren't there yet.

I'd argue that my implementation's advantage goes way beyond the mythical ?. operator. The ability to write a whole algorithm using a chain such as this means that you can inject your own creations (such as If , Do , etc.) and provide your own specialized logic.

I realize this is more complicated than what you're trying to do here, but it doesn't look like we're going to get a null-coalescing dot operator in C#5.

Simpler answer if the objects are cheap to create and you want to avoid null checks:

myOrder.NewIfNull().Customer.NewIfNull().City;

This will return either null or some initial value you set in the constructor or field initializer for City. NewIfNull isn't built-in, but it's real easy:

public static T NewIfNull<T>(this T input) where T:new()
{
   return input ?? new T();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM