I’ve spent some time investigating the ability to leverage C# Linq lambda expressions for retrieving name and value of .NET parameter or variable.

It would have allowed to achieve two objectives:

  • Make code shorter and reduce the duplication
  • Prevent the code from turning inconsistent after refactorings.

These two lines illustrate that clearly:

Enforce.Argument(eventEditorController, "eventEditorController");
Enforce.Argument(() => eventEditorController);

Second line is, obviously, shorter. Additionally, it will not become inconsistent after Visual Studio is used to rename eventEditorController to controller.

Well, any implementation spike had this “3 seconds per 1000000 operations” overhead. That’s because code like this:

Enforce.Argument(() => val);

is actually a replacement for this C# code:

Enforce.Argument<string>(
  Expression.Lambda<Func<string>>(
    Expression.Constant(val), new ParameterExpression[0]));

And internally creation of the LambdaExpression eventually hits delegate validation routines involving some reflection.

It may seem that this overhead is too small to worry about (just mere 3 seconds per 1000000 operations), but there are some side effects that I did want to consider.

Two scenarios that could benefit from this approach are sanity checks and validation/business rules.

First scenario is about leveraging sanity check shortcuts from the Lokad Shared Libraries:

// samples of current shortcuts
Enforce.Argument(user, "user")
Enforce.ArgumentNotEmpty(login, "login")

// design being considered
Enforce.Argument(() => user);
Enforce.ArgumentNotEmpty(() => login);

// actual shortcut replacements
if (null == user)
  throw new ArgumentNullException("user");

if (string.IsNullOrEmpty(login))
  throw new ArgumentNullException("login");

I think it would not be the best choice to leverage expressions in this scenario. Reasons:

  • The whole purpose of having these shortcuts is to make sanity checks as painless as possible. Spreading them around allows developers to build code that has less side effects and is more deterministic (aside from expressing design contracts in the code). If a developer would know about the performance implications of such sanity check shortcuts, then he would be less eager to leverage them.
  • Snippet like the one below can be easily produced by some refactoring tool. Although such code is logically inconsistent, it has not been known to break a system in production:
    Enforce.ArgumentNotEmpty(login, "userLogin")
    
  • Developers leveraging Lokad Shared and ReSharper don’t have to worry about the refactoring consistency, anyway – all sanity check methods are decorated with the attributes for R#. They tell it to rename parameter name while renaming the actual parameter reference.

Second Scenario is about making it easier to write and maintain validation and business rules like this one:

public static void ValidIdentity(Identity identity, IScope scope)
{
  scope.Validate(identity.Username, "Username", 
    StringIs.Limited(3, 256), StringIs.ValidEmail);
  scope.Validate(identity.Password, "Password", 
    StringIs.Limited(3, 64));
}

In this scenario the rule would look much better if it were written like:

public static void ValidIdentity(Identity identity, IScope scope)
{
  scope.Validate(() => identity.Username, 
    StringIs.Limited(3, 256), StringIs.ValidEmail);
  scope.Validate(() => identity.Password, 
    StringIs.Limited(3, 64));
}

Better readability is one of the reasons for using expression lambdas here.

Another reason lies within one of the extensibility points of this Application Block. It has been designed to allow automated binding of error messages directly to the UI controls via some ErrorProvider (i.e.: windows forms). Although this kind of functionality is still in evaluation phase, it already feels way more simple and efficient than any ASP.NET validators or custom UI-level rules. Better than that, it does not require any changes in existing rule libraries in order to reuse them at the UI level.

However, there is one requirement – parameter names must always stay in sync with the actual properties being validated to allow proper positioning of error messages. Obviously, implementing lambda-based syntax here helps to deal with this problem automatically.

And if this slight performance overhead in the business rules starts making some difference, then it is relatively easy to leverage another logical extensibility point and move rule library completely over to the external DSL (i.e. the one built with the Boo) till C# compiler gets mature enough to have an extensibility of its own. Code leveraging the rules would not even notice the difference.

Well, that’s pretty much it with the description of one of the numerous compromises and design considerations that compose the logic of Lokad Shared Libraries. The library itself will be updated soon.

Posted in C#