Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When to use Lazy vs Func

Tags:

c#

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?

like image 956
Luke101 Avatar asked Jun 14 '16 12:06

Luke101


3 Answers

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:

  1. You can use short-circuiting to avoid execution of expressions in a boolean expression.
  2. You can use Func to defer the execution (and possibly avoid it) no matter the context, so you can easily compose complex short-circuiting patterns.
  3. You can use 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.

like image 110
Luaan Avatar answered Nov 19 '22 04:11

Luaan


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.

like image 24
Evk Avatar answered Nov 19 '22 04:11

Evk


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.

like image 3
Anton Gogolev Avatar answered Nov 19 '22 04:11

Anton Gogolev