The Context Object

Scott Waldron Updated by Scott Waldron

Event handlers have the sender. But what do custom functions have? Wouldn’t it be nice if there was an analog to the sender parameter, but for every other type of function? Dino defines just such a thing by means of the context keyword. Any external function call can set a context callback which will be evaluated by Dino when it encounters the context keyword.

// let's assume that context is a Sale record with a Customer property.
def cmdCalculateDiscount(discount) {
var stdDiscount = context.Customer.DiscountLevel;
var totalDiscount = discount + stdDiscount;

retun totalDiscount;
}

Now, given the code above, imagine that you are editing a Sale record. You can see how context is powerful: it allows you to give the user access to custom commands that automatically “pick up” on the context in which they live. In our example, a Sale record editing screen. In the Customer editing screen, the context would be a Customer object. On a list screen, the context might be a list of the currently searched items.

Dino performs this by means of an IExecuter object which has a method that looks like this:

/// <summary>
/// Calls a function defined in the Dino code, and returns whatever
/// value it returns.
/// </summary>
/// <param name="functionName"></param>
/// <param name="args"></param>
/// <param name="includePrivate"></param>
/// <param name="contextArg"></param>
/// <returns></returns>
public sealed override dynamic CallFunction( string functionName,
object[] args, bool includePrivate, IContextArgument contextArg = null )
{
return DinoEvaluator.CallFunction( m_GlobalScope,
functionName, args, includePrivate, contextArg );
}

The executer simply forwards the current global scope to DinoEvaluator along with the context callback. The context is usually not available and should not be accessed in the global scope. It is intended primarily as an extra argument to a custom function (there is one useful exception to this that is covered below). The best way to think about context is as “sender for other functions that are not event handlers”. The following code snippet will make it clearer.

def cmdDoThis(aNum, aString) {
// context might be available
}

var discount = context.Discount; // error, context not available

def onPropertyChanged(sender, e) {
// sender is available, of course
}

var name = sender.Name; // error, of course sender would not be available here

Expression Evaluation with the Context

There is (of course!) one exception to the “context is only available in a function” rule. One of the stellar uses of Dino is as a powerful expression evaluator, and context can be critical in evaluating one-off expressions, making them far more useful and powerful. The DinoCompiler class defines a static method called Evaluate(…) which can accept a context object callback which may be accessed in the global scope.

/// <summary>
/// Evaluates statements / expressions and returns the result
/// of the last expression.
/// </summary>
/// <param name="script"></param>
/// <param name="getContext">
/// Wrapped in a weak context wrapper.
/// </param>
/// <param name="sandbox"></param>
/// <returns></returns>
public static dynamic Evaluate( ScriptOrString script,
Func<object> getContext = null, ISandbox sandbox = null )
{
using( var comp = new DinoCompiler() )
{
comp.InitializePrivate( script.GetScript(), sandbox,
null, null, null, getContext );

return comp.Result;
}
}

To simplify calling this method, it accepts a Func<object> argument for the context instead of the usual IContextArgument. Internally, the function wraps the Func<object> in a WeakFuncContextArg instance (see below). The Evaluate(…) function is incredibly useful to simplify unit testing Dino scripts.

But it is for far more than just unit testing. Using the expression evaluator, you can store expressions as properties of a record, then access them to perform dynamic calculations. This is very useful when you need to evaluate expressions for a single, important record, but not for other records of that type. Since the Dino expression lives on the record itself, you can create per-record evaluations.

The IContextArgument Interface

You may have noticed that the contextArg parameter in the CallFunction method of IExecuter is not a “callback” per se, but is an interface. This enables more flexibility and reuse in accessing the context, because you can create whatever kind of context classes you want, passing necessary objects to the constructor (dependency injection) to help with getting the ultimate object.

The IContextArgument interface couldn’t be much simpler. It defines a single method, GetContext(), which returns an object and takes no arguments.

/// <summary>
/// Used to pass a context callback to a script function from outside.
/// </summary>
public interface IContextArgument
{
/// <summary>
/// Resolves the context object.
/// </summary>
/// <returns></returns>
object GetContext();
}

Dino defines one built-in implementation of this interface in the form of the WeakFuncContextArg, the full code of which is shown here for reference.

/// <summary>
/// Holds a weak reference to a Func<object>. Do not pass anonymous
/// functions to the constructor of this object, because if so it will be
/// garbage collected almost immediately. Rather, pass in a func that is
/// stored as a local or member variable, or a property. See the examples for
/// guidance on this. Then, when the variable or property is eligible for
/// GC, the delegate passed to this will also be GC'ed.
/// <example>
/// <code>
/// BAD EXAMPLE:
/// var obj = new object();
/// var weakFunc = new WeakFuncContextArg(() => obj); // NO!!
/// GOOD EXAMPLE:
/// var obj = new object();
/// Func<object> func = () => obj; // or maybe a lot more complex...
/// var weakFunc = new WeakFuncContextArg(func);
/// </code>
/// </example>
/// </summary>
public sealed class WeakFuncContextArg : IContextArgument
{
private WeakReference<Func<object>> m_GetContext;

/// <summary>
/// Constructs a weak reference to a Func<object>. See examples
/// on class comments for correct calling convention.
/// </summary>
/// <param name="getContext"></param>
public WeakFuncContextArg( Func<object> getContext )
{
if( null == getContext )
throw new ArgumentNullException( "getContext" );

m_GetContext = new WeakReference<Func<object>>( getContext );
}

/// <summary>
/// Dereferences the weak reference and returns the result of
/// evaluating the func contained in it.
/// </summary>
/// <returns></returns>
public object GetContext()
{
Func<object> target;
m_GetContext.TryGetTarget( out target );
if( null == target )
return null;

return target();
}
}

How did we do?

Conditional Statements

FAQ

Contact