v4.1.5: Calculated Properties and Delegate Expressions

  Subscribe
4/4/2018 - (updated on 4/12/2018)

The summary below describes major new features, items of note and breaking changes. The full list of issues is in the release notes and is available to those with access to the Encodo issue tracker.

Highlights

Breaking changes

Tuple support for delegates

tl;dr: Applications might have to include the System.Tuple NuGet package in some assemblies.

This release adds an overload for creating delegate expressions that returns a Tuple (object, bool). This improvement allows applications to more easily specify a lambda that returns a value and a flag indicating whether the value is valid.

There are several overloads available for creating a DelegateExpression. The simplest of these assumes that a value can be calculated and is appropriate for constant values or values whose calculation does not depend on the IExpressionContext passed to the expression.

However, many (if not most) delegates should indicate whether a value can be calculated by returning true or false and setting an out object value parameter instead. This is still the standard API, but 4.1.5 introduces an overload that supports tuples, which makes it easier to call.

In 4.1.4, an application had the following two choices for using the "tryGetValue" variant of the API.

Elements.Classes.A.AddCalculatedProperty("FullText", f => f.CreateDelegate(GetFullText));

private object GetFullText(IExpressionContext context, out object value)
{
  var obj = context.GetInstance<IMetaObject>();
  if (obj != null)
  {
    value = obj.ToString();
		 
    return true;
  }

  value = null;
	
  return false;
}

If the application wanted to inline the lambda, the types have to be explicitly specified:

Elements.Classes.A.AddCalculatedProperty("FullText", f => f.CreateDelegate((IExpressionContext context, out object value) => {
  var obj = context.GetInstance<IMetaObject>();
  if (obj != null)
  {
    value = obj.ToString();
		 
    return true;
  }

  value = null;
	
  return false;
}));

The overload that expects a tuple makes this a bit simpler:

Elements.Classes.A.AddCalculatedProperty("FullText", f => f.CreateDelegate(context => {
  var obj = context.GetInstance<IMetaObject>();
	 
  return obj != null ? (obj.ToString(), true) : (null, false);
}));

Empty Expression Contexts

Previously, a DelegateExpression would always return false for a call to TryGetValue() made with an empty context. This has been changed so that the DelegateExpression no longer has any logic of its own. This means, though, that an application must be a little more careful to properly return false when it is missing the information that it needs to calculate the value of an expression.

All but the lowest-level overloads and helper methods are unaffected by this change. An application that uses factory.CreateDelegate<T>() will not be affected. Only calls to new DelegateExpression() need to be examined on upgrade. It is strongly urged to convert direct calls to the construct to use the IMetaExpressionFactory instead.

Imagine if an application used a delegate for a calculated expression as shown below.

Elements.Classes.Person.AddCalculatedProperty("ActiveUserCount", MetaType.Boolean, new DelegateExpression(GetActiveUserCount));

// ...

private object GetActiveUserCount(IExpression context)
{
  return context.GetInstance<Person>().Company.ActiveUsers;
}

When Quino processes the model during startup, expression are evaluated in order to determine whether they can be executed without a context (caching, optimization, etc.). This application was safe in 4.1.4 because Quino automatically ignored an empty context and never called this code.

However, as of 4.1.5, an application that calls this low-level version will have to handle the case of an or insufficient context on its own.

It is highly recommended to move to using the expression factory and typed arguments instead, as shown below:

Elements.Classes.Person.AddCalculatedProperty<Person>("ActiveUserCount", MetaType.Boolean, f => f.CreateDelegate(GetActiveUserCount));

// ...

private object GetActiveUserCount(Person person)
{
  return person.Company.ActiveUsers;
}

If you just want to add your own handling for empty contexts, you can do the following (note that you have to change the signature of GetActiveUserCount):

Elements.Classes.Person.AddCalculatedProperty("ActiveUserCount", MetaType.Boolean, new DelegateExpression(GetActiveUserCount));

// ...

private bool GetActiveUserCount(IExpression context, out object value)
{
  var person = context.GetInstanceOrDefault<Person>();
  if (person == null)
  {
    value = null;
    return false;
  }

  value = person.Company.ActiveUsers;

  return true;
}

Sign up for our Newsletter