I have multiple boolean statements being evaluated. They are evaluated in order from top to bottom. Some boolean statements are expensive to evaluate so I am use lazy on them. Here is the sample code:
bool contactHasPermission = ContactHasPermission(id);
var contactIsInRole = new Lazy<bool>(() => IsContactInRole(contactId, roleType)); //Database call
var contactHasAtLeastXPoints = new Lazy<bool>(() => ContactHasXPoints(contactId, 100)); //database call
bool permit = contactHasPermission || contactIsInRole.Value || contactHasAtLeastXPoints.Value;
As you can see the most expensive statements are in lazy objects. I could have done the same thing using Func. What the the pros and cons of using one over the other?
Neither Lazy
nor Func
are magic. The only thing they provide is that you defer the execution of the code in question. It doesn't make it faster, it doesn't make it parallel, it just does it later.
The main benefit from using deferred execution comes when you can avoid the execution entirely in some cases. For example, if you have a piece of code like this:
a() && b() && c()
you know that it only makes sense to evaluate b()
and c()
when a()
returns true. The deferred execution of b()
and c()
never happens. You lose this property when you store the intermediate results in locals (for example, to improve readability):
var resultOfA = a();
var resultOfB = b();
var resultOfC = c();
...
return resultOfA && resultOfB && resultOfC;
There's no longer any opportunity to defer the execution of b()
, unless you use horrible code like var resultOfB = resultOfA && b();
. However, you can also inline the function to be evaluated, rather than the result of the function to be evaluated.
The simplest way to do this in C# is using a function delegate:
Func<bool> fa = () => a();
Func<bool> fb = () => b();
Func<bool> fc = () => c();
...
return fa() && fb() && fc();
However, this means that whenever you execute fa
, for example, you evaluate a()
again and again. That might be desired or not. If it's not desired, you come to the world of Lazy
.
Lazy
allows you to defer execution, but cache the result of the execution so that any subsequent accesses to the lazy object will just use the result of the original deferred execution, rather than evaluating the "constructor" function again.
So, in summary:
Func
to defer the execution (and possibly avoid it) no matter the context, so you can easily compose complex short-circuiting patterns.Lazy
to defer the execution (and possibly avoid it), while also caching the result for any subsequent evaluations of the lazy's value. This is particularly useful for long-lived objects with non-trivial scope.In your sample code, the best choice is probably 1) - no point in getting the overhead of function delegates, when you can use short-circuiting. The main benefit of deferred execution here is to allow you to name parts of the expression - so that you get e.g. hasPermission() && isInRole() && isSunny()
instead of a hard to read expression. If you only use each part of the expression at most once, there's no point in using Lazy
- it just means extra boilerplate around the underlying Func
.
If you use your boolean condition only once - there seems to be no considerable difference between Lazy and Func in your case. However if you do use them more than once then of course Lazy is preferrable - it will actually compute value just once and will return cached value on next calls.
Note that because boolean conditions are already lazily evaluated, you can just do:
bool permit = contactHasPermission || IsContactInRole(contactId, roleType) || ContactHasXPoints(contactId, 100)
and if will have the same effect - expensive calls will not be evaluated unless necessary. However if you want to do that for readability, and you don't reuse your boolean conditions - use Func.
In your particular scenario, neither Func
nor Lazy
make sense here. C# short-circuits evaluations, so
bool permit = contactHasPermission ||
IsContactInRole(contactId, roleType) ||
ContactHasXPoints(contactId, 100);
will do just fine: if contactHasPermission
is true
, no database calls will be made.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With