Reproducing Promise IoC in C#
Another diversion before getting back to actual Promise language syntax description, this time trying to reproduce the Promise IoC syntax in C#. Using generics gets us a good ways there, but we do have to use a static method on a class as the registrar giving us this syntax:
$#[Catalog].In(:foo).Use<DbCatalog>.ContextScoped;
// becomes
Context._<ICatalog>().In("foo").Use<DbCatalog>().ContextScoped();
Not too bad, but certainly more syntax noise. Using a method named _ is rather arbitrary, i know, but it at least kept it more concise. Implementation-wise there's a lot of assumptions here: This approach forces the use of interfaces for Promise types, which can't be enforced by generic constraints. It would also be fairly simple to pre-initialize the Context
with registrations that look for all interfaces IFoo
and then find the implementor Foo
and register that as the default map, mimicking the default Promise behavior by naming convention instead of Type/Class name shadowing.
Next up, instance resolution:
This is where the appeal of the syntax falls down, imho. At this point you might as well just go to constructor injection, as with normal IoC. Although you do need that syntax for just plain inline resolution.
And the whole thing uses a static class, so that seems rather hardcoded. Well, at least that part we can take care of: If we follow the Promise assumption that a context is always bound to a thread, we can use [ThreadStatic]
to chain context hierarchies together so that what looks like a static accessor is really just a wrapper around the context thread state. Given the following Promise syntax:
context(:inner) {
$#[Catalog].In(:inner).ContextScoped;
$#[Cart].ContextScoped;
var catalogA = Catalog();
var cartA = Cart();
context {
var catalogB = Catalog(); // same instance as catalogA
var catalogC = Catalog(); // same instance as A and B
var cartB = Cart(); // different instance from cartA
var cartC = Cart(); // same instance as cartB
}
}
we can write it in C# like this:
using(new Context("inner")) {
Context._<ICatalog>().In("inner").ContextScoped();
Context._<ICart>().ContextScoped();
var catalogA = Context.Get<ICatalog>();
var cartA = Context.Get<ICart>();
using(new Context()) {
var catalogB = Context.Get<ICatalog>(); // same instance as catalogA
var catalogC = Context.Get<ICatalog>(); // same instance as A and B
var cartB = Context.Get<ICart>(); // different instance from cartA
var cartC = Context.Get<ICart>(); // same instance as cartB
}
}
This works because Context
is IDisposable
. When we new up an instance, it takes the current threadstatic and stores it as it's parent and sets itself as the current. Once we leave the using()
block, Dispose()
is called, at which time, we set the current context's parent back as current, allowing us to build up and un-roll the context hierarchy:
public class Context : IDisposable {
...
[ThreadStatic]
private static Context _current;
private Context _parent;
public Context() {
if(_current != null) {
_parent = _current;
}
_current = this;
}
public void Dispose() {
_current = _parent;
}
}
I'm currently playing around with this syntax a bit more and using Autofac inside the Context to do the heavy lifting. If I find the syntax more convenient than plain Autofac, i'll post the code on github.