Developer

Hands-on programming: Create a function library from an external configuration

Justin James incorporates information from his T4 templates and lambda expressions columns into this tutorial on creating a function library.

 

Some of my Code Concepts articles and Hands-on programming posts are building towards something bigger: a small compiler and parser of sorts — a simple Reverse Polish Notation calculator. In this column, I integrate a few of the pieces together.

I'll show how to create a generic class that can be retrieved by name and a class that inherits from the generic class and pre-loads itself with functions stored in an external configuration at compile time. (In case you missed my articles about T4 templates and lambda expressions, I suggest reviewing them before reading this article if you are not familiar with those ideas.)

Note: This blog post is also available as a PDF download.

Example

Our generic class is pretty easy to write. Internally, it will use a Dictionary object to hold the functions and support a few simple methods (Add(), Remove(), Get(), and an indexer). Here's the code:

public class FuncLibrary<T1, TResult>

{

private Dictionary<string, Func<T1, TResult>> _functions;

public Func<T1, TResult> this[string name]

{

get

{

return _functions[name];

}

set

{

_functions[name] = value;

}

}

public void Add(string name, Func<T1, TResult> function)

{

_functions.Add(name, function);

}

public bool Remove(string name)

{

return _functions.Remove(name);

}

public Func<T1, TResult> Get(string name)

{

return this[name];

}

public FuncLibrary()

{

_functions = new Dictionary<string, Func<T1, TResult>>();

}

}

You might be wondering why the first type is T1 and not T; you may also notice that the functions it stores can only accept one parameter. I call the type T1 because it is an exercise for you to copy/paste the code into a new class that accepts two parameters and make the needed changes. For my purposes, I made five versions, accepting from 0 to 4 parameters.

Now that we have a useful, generic class, it is time to look at our specific needs. For our RPN calculator, I decided that it will work in a fairly Lisp-like fashion, operating entirely on lists. As a result, all of the function inputs and outputs will be List<float> objects. What we need to do now is make a new class that subclasses FuncLibrary<T1, TResult>:

public class Functions : FuncLibrary<List<float>, List<float>> {

}

So far, so good. But, how will we pre-load this library with functions at compile time from an external source? This is where the T4 templates come into play.

First, we will make a simple XML file that can hold our functionality. Here is a truncated version of that XML file:

<?xml version="1.0" encoding="utf-8" ?>

<functions>

<function name="add">

<![CDATA[

var result = input[0];

for (int counter = 1; counter <= input.Count - 1; counter++) {

result += input[counter];

}

return new List<float>{ result };

]]>

</function>

</functions>

If you look at the code within the CDATA block, you may wonder where the variable input came from — that is the name our system will give to the input to each function when it creates the lambda expressions. Remember, the input is of type List<float>; the T4 template will parse this XML and create lambda expressions with this data and add it to our new Functions class in the constructor. Let's get started on the template.

It should start with a number of directives to declare the language as C# 3.5 (if you just declare it as C#, we won't have access to things like var, which would be a problem for the LINQ), reference the needed assemblies and load the namespaces the T4 code will need:

<#@ template language="C#v3.5" #>

<#@ assembly name = "System.Core" #>

<#@ assembly name = "System.Xml" #>

<#@ assembly name = "System.Xml.Linq" #>

<#@ import namespace = "System" #>

<#@ import namespace = "System.Linq" #>

<#@ import namespace = "System.Xml" #>

<#@ import namespace = "System.Xml.Linq" #>

<#@ import namespace = "System.IO" #>

Next, we put in the body of our class, including any using statements that the defined functions might use:

public class Functions : FuncLibrary<List<float>, List<float>> {

public Functions() {

}

}

Now we need to fill in the constructor, but put T4 code in it that does the heavy lifting:

<#

var functionListLocation = @"C:\Users\jjames\Documents\Source Control Working Copy\TitaniumCrowbarUtilities\ExtendableRPNCalculator\FunctionList.xml";

var rawXml = File.ReadAllText(functionListLocation);

var functionList = XDocument.Parse(rawXml);

var functions = from function in functionList.Root.Elements() select function;

foreach (var item in functions)

{

var funcName = item.Attribute("name").Value;

var lambdaName = "lambda_" + funcName;

this.Write("Func<List<float>, List<float>> " + lambdaName + " = input => {\n");

this.Write(item.Value);

this.Write("};\n");

this.Write("Add(\"" + funcName + "\", " + lambdaName + ");\n");

}

#>

The first line of code is the hardcoded path to the XML file; I haven't found a good way around this yet. Next, we read in the XML file and load it into an XDocument object. We iterate through each of the function elements in the XML document, and for each one, we extract the name attribute and store it in the funcName variable. We then create a variable name for the generated code to hold the new lambda expression in, making sure that it will be unique by prefixing it with lambda_ (ugly, I know). Then we write to the output stream, first defining the lambda expressions by outputting a variable declaration and creating a lambda, then writing the contents of the function element, and finally, closing up the lambda definition and ending the variable declaration. The last thing we do is call the class's Add() method with the new function's name and the lambda expression. The T4 template gets run any time we tell Visual Studio to "Save," even if the contents have not changed. Go ahead and do so, and take a look at the output (for me, it gets put into Functions1.cs since I named my template file Functions.cs):

using System;

using System.Collections.Generic;

using FunctionLibrary;

public class Functions : FuncLibrary<List<float>, List<float>> {

public Functions() {

Func<List<float>, List<float>> lambda_add = input => {

var result = input[0];

for (int counter = 1; counter <= input.Count - 1; counter++) {

result += input[counter];

}

return new List<float>{ result };

};

Add("add", lambda_add);

}

}

Pretty neat, huh? If it doesn't seem particularly useful at the moment, consider the possibilities. The code could be stored in a database, allowing you to maintain a system where customer-specific modifications can be loaded at compile time without needing to touch to core code. You could also create a Domain Specific Language (DSL) for the code in the configuration, allowing business users or other specialists to create the functionality that gets turned into actual .NET code when you compile everything together. The possibilities are limitless, and soon we will have a parser to start making calls to our function library.

J.Ja

Disclosure of Justin's industry affiliations: Justin James has a contract with Spiceworks to write product buying guides; he has a contract with OpenAmplify, which is owned by Hapax, to write a series of blogs, tutorials, and articles; and he has a contract with OutSystems to write articles, sample code, etc.

———————————————————————————————————————————-

Get weekly development tips in your inbox Keep your developer skills sharp by signing up for TechRepublic's free Web Developer newsletter, delivered each Tuesday. Automatically subscribe today!

About Justin James

Justin James is the Lead Architect for Conigent.

Editor's Picks

Free Newsletters, In your Inbox