Well… The C# 3.0 compiler has been out for a couple of months now. I assume plenty of people are using it (albeit calling it “VS 2008”). I also assume, judging by the fate of C# 2.0, that a lot more people are actually programming in C# 1.0/2.0, just under VS 2008. Long live backwards compatibility!
This will be my humble attempt to show some things that you can do in C# 3.0, that you maybe could have done in 2.0 with an uglier syntax and that you surely couldn’t have done in 1.0 (by humane methods).
Disclaimer: you may consider a lot things here an abuse of the new lambda syntax, an abuse of reflection and and downright violation of “traditional C# programming practices”. Good for you. That’s your opinion. Just make sure you don’t end up like this guy that considers these features “subversive to business interests”.
The first thing that popped on my mind are dynamic variables. Present in Lisp, Perl and a whole list of other languages I have no clue about, dynamic variables are basically variables whose value is resolved at runtime by searching up the stack for their definition. Yeah.. that came out weird, let me explain.
There are two types (the perl6 guys might disagree) of variables: lexically scoped and dynamically scoped.
Lexically scoped variables are the regular variables: you define one, you make a closure around it, you return the closure, and when someone calls it, the enclosed variable will have the value that was set in the scope where is was defined. Just for the sake of it (and because it’s pretty in C# 3.0), here’s the classic accumulator function where ‘t’ is a lexically scoped variable:
// You can paste this code inside any C# method. Main() is good.
Func<Func<int>> Accum = () => {
var t = 0;
return () => ++t;
}
var acc1 = Accum();
var acc2 = Accum();
acc1().Print(); // prints "1"
acc1().Print(); // prints "2"
acc2().Print(); // prints "1"
acc1().Print(); // prints "3"
Now, for dynamically scoped variables. Fist of all, why do we need a new kind of variables?
Let’s say you’re working on an application for managing.. uhm… cows. Kind of like an ERP for farmers. Suppose you use some logger (I assume you’re not just spitting out exceptions to stderr or something). Let’s call it Logger, with methods like… Logger.Log(Exception ex). Brilliant! Now, what you come to realize after some usage is that the exceptions alone are not helping you that much because pretty much the only useful information they give you is the line number. So what we need is some context.
Let’s say we add the Log(Exception ex, Dictionary context) method to our logger. First of all, a good idea may be to log the currently logged in user and the time when the error happened. Most loggers (log4net, NLog) support specifying things like this in their configuration (albeit it’s a clear violation of the separation-of-concerns principle). But soon you realize that you want different contexts for different (kinds of) actions, like logging the ID/number/name of the current cow if errors occur while viewing information about a specific cow, so you can easily see which cow is responsible for the error. And maybe we want some different context logged when calling a method that processes an order for some cattle from some customers, like the customer’s name, email and the quantity he intends to buy. The logger’s configuration, as bright as it could be, is by no means a solution.
One (dumb) way to achieve this is to actually carry the context through the calls as an additional parameter:
public void ProcessCommand(Command command) {
//do some stuff
var context = new Dictionary<string, object> {
{"client", command.Client.Name},
{"client", command.Client.Email},
{"Command quantity", command.Quantity}};
AddTaxes(command, context);
//do some other stuff
}
private void AddTaxes(Command command, Dictionary<string, object> context) {
try {
//...
}
catch(Exception ex) {
Logger.Log(ex, context);
throw;
}
}
For reasons that I won’t discuss here, this is kind of dumb.
Another (maybe dumber) way of keeping the context is by adding a “Context” field to whatever class you’re in. First, this doesn’t work, since obviously it assumes that all the methods which write to the log that might get called somewhere along the way are from the same instance as the “context” field. Secondly, such a field would pollute the model with strange and unnecessary things in a lot of places.
You could, of course, do this in a lot of ways. But, since I mentioned dynamically scoped variables some time ago, we’ll use DynVar. This is a class for that provides static methods for defining and binding dynamically scoped variables of type T. The “Set” method if defined as:
public static void Set(T value, Action<T> scope) {...}
The first parameter is the value you want to set. The second is a scope in which the variable will be defined, which is actually a lambda expression of type Action<T>. When the scope terminates, the variable defined will also unbound (“deactivated”, so to speak). The name of the variable is actually obtained from the name of the parameter of the Action<T> lambda expression passed as the scope parameter.
So, by using an Action<T> as the type of the scope we have obtained three things:
- we can easily define the scope for the variable
- we provided a name for it (from the parameter’s name)
- we actually bind the variable inside (and only inside) the scope
The example above becomes:
public void ProcessCommand(Command command) {
DynVar<Dictionary<string, object>>.Set(
new Dictionary<string, object> {
{"client", command.Client.Name},
{"client", command.Client.Email},
{"Command quantity", command.Quantity}},
loggerContext => {
//do some stuff
AddTaxes(command); // no more context, yay!
//do some other stuff
});
}
The first parameter is the dictionary with the context. The second is an Action<object> that has the exact code previously contained in the body of our method. The scope’s parameter name, “loggerContext”, indicates the name of the variable bound. Also, the context is bound inside the scope in the parameter, so if we want to modify it we could easily do so.
So if “Set” is only for defining variables, how does one use them? With the “Use” method:
public static Use(Action<T> scope) {...}
This binds the value of a dynamically scoped variable (or the value null if the variable hasn’t been previously defined) to the parameter of the scope. For example, the Logger.Log method now looks like this:
public static void Log(Exception ex) {
DynVar<Dictioary<string, object>>.Use(loggerContext => {
//concatenate all the values from the context, the exception and log it somewhere
});
}
So.. what have we improved? Well, for once there’s no parameter passed around and there is no magic field holding the context. And this works for any call to Logger.Log that occurred somewhere inside the scope received by set, no matter how deep the call stack was or what class the method was defined in.
But this is not all. We can redefine an existing variable, use it inside a scope, and then, when the scope ends, the variable will return to it’s previous value. Kind of like a stack. Here’s a simple example:
void F1(int i) {
DynVar<Dictionary<string, object>>.Set(
new Dictionary<string, object> {{"F1's context", i}},
loggerContext => {
Logger.Log(null);
F2(i + 1);
Logger.Log(null);
});
}
void F2(int i) {
DynVar<Dictionary<string, object>>.Set(
new Dictionary<string, object> {{"F2's context", i}},
loggerContext => Logger.Log(null));
}
The call F1(1) will log:
F1’s context: 1
F2’s context: 2
F1’s context: 1
This is what happens: The dynamic variable loggerContext is first defined in F1 so Logger.Log logs “F1’s context”. Then, when F2 is called, the variable is re-declared with a new value. This causes the logger to log “F2’s context”. After the declared in F2 terminates (right before F2 itself terminates), the loggerContext variable returns to it’s previous value, the one declared before in F1. Hence, Logger.Log again logs “F1’s context”.
So, let’s try to sum up some properties discussed so far for dynamically scoped variables:
- They are defined over a scope.
- They exist as long as the scope executes.
- They can be accessed from anywhere as long as the defining scope is alive.
- A scope can contain yet another self-contained definition for the same variable.
- When a scope exits, the variable returns to it’s old value (or null, if none was defined)
What about thread safety?
Well, dynamic variables usually follow the same semantics as the old Lisp-machines: a variables is only accessible from the same thread where it was defined. This can be easily implemented with thread-local storage. As for ASP.NET, the HttpContext.Request.Items makes a perfect host for keeping the values. So yes, they are thread-safe and, with minor adjustments, can be made ASP.NET-safe.
There are plenty of cases where a dynamic variable can prove useful, but the context example is the most common of all. A lot of times you’re gonna need to store some data somewhere and access it down the stack and OOP is going to be awkward.
Here’s DynVar.cs:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace Test
{
public class DynVar<T>
{
public static void Set(T value, Action<T> scope)
{
string name = scope.Method.GetParameters()[0].Name;
Set(value, name, scope);
}
public static void Set(T value, string name, Action<T> scope)
{
var slot = Thread.GetNamedDataSlot(name);
Stack<T> values = Thread.GetData(slot) as Stack<T>;
if (values == null) {
values = new Stack<T>();
Thread.SetData(slot, values);
}
values.Push(value);
try {
scope(value);
} finally {
values.Pop();
Thread.SetData(slot, values);
}
}
public static void Use(Action<T> action)
{
string name = action.Method.GetParameters()[0].Name;
Use(name, action);
}
public static void Use(string name, Action<T> action)
{
var slot = Thread.GetNamedDataSlot(name);
Stack<T> values = Thread.GetData(slot) as Stack<T>;
if (values == null || values.Count == 0)
action(default(T));
else
action(values.Peek());
}
}
}