Skip to content

sdether/BinaryFinery.IOC

 
 

Repository files navigation

BinaryFinery.IOC

BinaryFinery.IOC is a simple Inversion of Control and Dependency Injection framework. BF.IOC is unlike "traditional" IOC containers, because the containers are defined using .NET Interfaces, not XML, not dictionaries, not built using delegates etc.

This is version 1.0. A very basic 1.0. I wouldn't recommend using it yet, except for the most basic of purposes. OTOH, how you use the system isn't going to change: the system uses interfaces and thats about it. There will be more features coming: it is a WIP.

Note: For now, the system relies on a manually generating the contexts. See "HACK" below.

What's in the current version:

  1. Singleton generation.
  2. Object injection using constructors only. Which is actually a very bad habit so don't do it.
  3. Detects cyclic dependencies (throws exception)

New:

  1. Injection using properties. Add an [Inject] attribute.

Future versions:

  1. Injection using methods.
  2. Context hiearchies.
  3. Post-construction events / methods.

How To Use It

A library will define a base context: a group of classes/interfaces that it will expect to work together:

public interface IDependencyTestBaseContext : IContext
{
    IFoo FooP { get; }
    IDeps DepsP { get; }
}

This is a basic context. The names of the properties are unimportant. If you have an instance of IDependencyTestBaseContext you can get an IFoo and an IDeps.

The application will then define specific types that it needs created using Implementation attributes:

public interface IDependencyTestContext : IDependencyTestBaseContext
{
    [Implementation(typeof(Foo))]
    IFoo FooP { get; }
    [Implementation(typeof(Deps))]
    IDeps DepsP { get; }
}

The application can then realise this context as follows:

CM = ContextSystem.Manager;
var context = CM.Create<IDependencyTestContext>();
var result = context.DepsP;
Assert.That(result,Is.Not.Null);

Specialisation

It is possible to specialise further by creating more specialised interfaces:

public interface IDependencyTestContextAttributed : IDependencyTestContext
{
    [Implementation(typeof(Foo))]
    IFoo FooP { get; }
    [Implementation(typeof(DepsAttributed))]
    IDeps DepsP { get; }
}

The system requires that class DepsAttributed is a specialization of class Deps. This is incredibly useful when you have hierarchies of libraries which each become more specialized. For example, I have BinaryFinery.Utils which implements logging and exception handling. I then have BinaryFinery.Touch, which is a MonoTouch only library. Some of these classes require that the particular logging or exception classes be MonoTouch-specific. Finally my application may have further specialization.

The HACK

At this point, I have not yet implemented automatic generation of Context implementations because a) I didn't need to to get the tests to pass and b) I'm doing MonoTouch development and I can't use reflection for that anyway. So for now, there is a missing step:

public class DependencyTestContextImpl : BaseContextImpl, IDependencyTestBaseContext
{
    public IFoo FooP
    {
        get { return (IFoo) Factory.ObjectForProperty("FooP"); }
    }

    public IDeps DepsP
    {
        get { return (IDeps) Factory.ObjectForProperty("DepsP"); ; }
    }
}

As you can see, this basic version of a Context implementation will be easy to generate using Emit, but for now, you have to do it yourself. You must also manually register this class with the ContextManager:

CM = ContextSystem.Manager;
CM.RegisterCustomContextImplementation(typeof(DependencyTestContextImpl), typeof(IDependencyTestBaseContext));

A nice shortcut is that if you have specializations of IDependencyTestBaseContext that dont add any more properties (i.e. they just slap Implementation attributes on existing ones), then you can easily reuse the custom implementation like so:

public class DependencyTestContextTop : DependencyTestContextImpl, IDependencyTestContextAttributed, IDependencyTestContext2 {}
CM.RegisterCustomContextImplementation(typeof(DependencyTestContextTop), typeof(IDependencyTestContextAttributed));
CM.RegisterCustomContextImplementation(typeof(DependencyTestContextTop), typeof(IDependencyTestContext2));

Future version will generate the context at runtime using Emit, or tools will create this class for you automatically for use with MonoTouch.

Cyclic Dependencies in Constructors

There are a couple of ways around cyclic dependencies in constructors, though all of them will throw an exception in the case that a dependency is actually used.

  1. Use object allocation followed by object initialization.
  2. Use wrappers if the dependency is an interface.

Method 1 is not available in silverlight, nor I suspect MonoTouch. Method 2 is only available if one of the cyclic dependencies is an interface, and only then if code can be generated. These methods allow the cycle to be broken such that the first one created is given references to either an unitialized object, or an uninitialized proxy. If the constructor attempts to use that dependency then it will be handed an exception, or worse, it will fail at some later time.

As a result, BF.IOC will never handle cyclic dependencies in constructors other than to throw an exception.

How dependencies are resolved

Dependencies are resolved primarily using types. Simply if Foo has constructor Foo(IBar b) then when Foo is constructed, the factory will look for a property that can supply an IBar. However, take this set of contexts:

public interface ILocationDependencyContext2 : ILocationDependencyContext
{
    [Implementation(typeof(Foo))]
    IFoo A { get; }
}

public interface IAnotherLocationDependencyContext2 : IAnotherLocationDependencyContext
{
    [Implementation(typeof(OtherFoo))]
    IFoo B { get; }

    [Implementation(typeof(DontCareDeps))]
    IDontCareDeps Deps { get; }
}

public interface ICompoundContext : ILocationDependencyContext2, IAnotherLocationDependencyContext2
{
    
}

In this example, class DontCareDeps requires an IFoo. It doesnt care what kind of IFoo. I just wants one. However, the author of IAnotherLocationDependencyContext2 expects that this instance of DontCareDeps should use class OtherFoo. What should happen when we ask ICompoundContext for its Deps? It should still build Deps using OtherFoo from property B, not Foo from property A. So the simple type-only resolution from v 1.0 isn't quite good enough. The system will resolve ambiguities for a property X by looking where property X was initially declared - i.e. the most general interface. In the case where X is actually compatibly defined in more than one "base" interface, the behavior is "not defined", i.e. it will use the first one it finds, and this can depending on circumstance, build version, optization etc.

Depdendency Fragments

public interface IFragmentedContext : IContext
{
    [Implementation(typeof(DontCareDeps))]
    IDontCareDeps Deps { get; }
}

A "dependency fragment" is one where an Implementation specifies a class whose dependencies cannot be resolved. In the above case, the concrete implementation DontCareDeps requires an IFoo in its constructor. The IFragmentedContext does not declare an IFoo. It is considered broken. The dependency must be resolvable in the base contexts of that interface. The following is not enough:

public interface IMissingPieceContext : IContext
{
    [Implementation(typeof(OtherFoo))]
    IFoo B { get; }
}

public interface IJoinContext : IFragmentedContext, IMissingPieceContext
{
    
}

The IJoinContext does have an IFoo (from IMissingPieceContext), but it will not be used to resolve the dependency in IFragmentedContext. This is by design to reduce fragile behavior.

However, a fragmented interface can be repaired later, by redoing the implentation in a context which can resolve the dependency (directly or indirectly). Such as:

public interface IWorkingJoinContext : IFragmentedContext, IMissingPieceContext
{
    [Implementation(typeof(DontCareDeps))]
    IDontCareDeps Deps { get; }
}

Background

This is my fourth IOC/DI framework. The previous three were written several years ago, and the third iteration is still in use today in a robust C# graphical application. I have not looked at that code for several years and this is a clean implementation using test driven development to create what I need for my new apps.

About

An IoC / DI container in C#

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published