An easy to use, hassle free, Inversion of Control Container for small projects and beginners alike.
Welcome to TinyIoC
Welcome to TinyIoC - an easy to use, hassle free, Inversion of Control Container. TinyIoC has been designed to fulfil a single key requirement - to lower the "level of entry" for using an IoC container; both for small projects, and developers who are new to IoC who might be "scared" of the "big boys"!
To that end, TinyIoC attempts to stick to the following core principals:
- Simplfied Inclusion - No assembly to reference, no binary to worry about, just a single cs file you can include in your project and you're good to go. It even works with both Mono and MonoTouch for iPhone development!
- Simplified Setup - With auto-resolving of concrete types and an "auto registration" option for interfaces setup is a piece of cake. It can be reduced to 0 lines for concrete types, or 1 line if you have any interface dependencies!
- Simple, "Fluent" API - Just because it's "Tiny", doesn't mean it has no features. A simple "fluent" API gives you access to the more advanced features, like specifying singleton/multi-instance, strong or weak references or forcing a particular constructor.
As well as the base container, the TinyIoC project also includes an Event Aggregation/Messenger system called TinyMessenger. TinyMessenger allows for publishing of and subscribing to "messages" in a loosely coupled manner. TinyMessengeris independent of TinyIoC but if you uncomment line 18 (#define TINYMESSENGER) in TinyIoC.cs it will automatically register TinyMessenger in your root container) but not in any child containers by default. For more information see the TinyMessenger page.
TinyIoC is released under the Ms-PL license - which /should/ mean (and I'm certainly no lawyer) you can include it in commercial and non-commercial software, but you can't take it and re-release it under an incompatible licence such as GPL. More information can be found on the licensing page. If you feel this licence doesn't fit your requirements feel free to get in touch.
Key Feature Examples
Simple Setup
| // TinyIoC provides a lazyily constructed singleton
// version of itself if you want to use it.
var container = TinyIoCContainer.Current;
// By default we can resolve concrete types without
// registration
var instance = container.Resolve<MyConcreteType>();
// We can automatically register all concrete types, abstract base classes
// and interfaces with a single call.
container.AutoRegister();
var implementation = container.Resolve<IMyInterface>();
// By default we silently ignore duplicate implementations,
// but we can choose to throw a meaningful exception that contains
// the interface/base type and all the potential implementations:
container.AutoRegister(false);
|
Registration API
| // We support name and unnamed registrations
container.Register<MyConcreteType>(); // Unnamed
container.Register<MyConcreteType>("Name"); // Named
// By default we register concrete types as
// multi-instance, and interfaces as singletons
container.Register<MyConcreteType>(); // Multi-instance
container.Register<IMyInterface, MyConcreteType>(); // Singleton
// Fluent API allows us to change that behaviour
container.Register<MyConcreteType>().AsSingleton(); // Singleton
container.Register<IMyInterface, MyConcreteType>().AsMultiInstance(); // Multi-instance
// We also allow delegate facories and instance
// registrations (for both concrete and interfaces)
// We default to strong references..
container.Register<MyConcreteType>((c,p) => MyConcreteType.GetNew()); // Delegate factory
var instance = MyConcreteType.GetNew();
container.Register<IMyInterface, MyConcreteType>(instance); // Instance
// ..but can use the fluent API to use
// weak references instead.
container.Register<MyConcreteType>((c,p) => MyConcreteType.GetNew()).WithWeakReference();
container.Register<IMyInterface, MyConcreteType>(instance).WithWeakReference();
// By default we try and find the best constructor
// at resolve time, but we can use the fluent API
// to specify one.
//
// This example forces this overload:
// public MyConcreteType(IMyInterface dependency, int property1, string property2)
container.Register<MyConcreteType>().UsingConstructor(
() => new MyConcreteType(null as IMyInterface, 1, ""));
|
Resolution API
| // Basic named and unnamed resolution
var instance = container.Resolve<MyConcreteType>(); // Unnamed
instance = container.Resolve<MyConcreteType>("Name"); // Named
// By default we try and resolve every concrete type,
// whether they are registered or not, but we don't "fallback"
// to an unnamed registration if resolving a named one.
//
// We can easily change that behaviour though.
container.Resolve<MyConcreteType>(
new ResolveOptions() {UnregisteredResolutionAction = UnregisteredResolutionActions.Fail});
container.Resolve<MyConcreteType>("Name",
new ResolveOptions() { NamedResolutionFailureAction = NamedResolutionFailureActions.AttemptUnnamedResolution });
// We can pass parameters using name/value pairs.
// By default the "greediest" constructor is used if possible,
// but we will fall all the way back to a default constructor if
// necessary to construct the type.
container.Resolve<MyConcreteType>(new NamedParameterOverloads() { { "property1", 12 }, { "property2", "Testing" } });
// We also have the same overloads on CanResolve
// to determine if resolution is possible
container.CanResolve<MyConcreteType>(
new ResolveOptions() { UnregisteredResolutionAction = UnregisteredResolutionActions.Fail });
container.CanResolve<MyConcreteType>("Name",
new ResolveOptions() { NamedResolutionFailureAction = NamedResolutionFailureActions.AttemptUnnamedResolution });
container.CanResolve<MyConcreteType>(new NamedParameterOverloads() { { "property1", 12 }, { "property2", "Testing" } });
// And also for TryResolve, which will resolve if possible
// and return a boolean to indicate whether resolution completed
var resolved = container.TryResolve<MyConcreteType>("MyName", out resolvedType); // resolved is true if resolution was possible
|
Constructor Injection
| // By default we will use the "greediest" constructor
// if we can..
public Constructors(ITestInterface1 dependency, string value1, int value2)
{
}
// .. but we will fallback if we can't - rather than
// throwing a resolution exception.
public Constructors(ITestInterface1 dependency)
{
}
// For types that are expensive to construct and may not
// always be needed we provide automatic lazy factories
// by putting Func<T> in the constructor
public Constructors(Func<MyExpensiveObject> expensiveObjectFactory)
{
var instance = expensiveObjectFactory.Invoke();
}
// We also allow lazy factories that specify a named registration
// to resolve using Func<String, T>:
public Constructors(Func<String, MyExpensiveObject> expensiveObjectFactory)
{
var instance = expensiveObjectFactory.Invoke("MyNamedRegistration");
}
// And we also allow you to specify named parameter overloads to lazy factories
// using Func<String, IDictionary<string, object>, T>:
public Constructors(Func<string, IDictionary<String, object>, TestClassWithParameters> factory)
{
_Factory = factory;
Prop1 = _Factory.Invoke("", new Dictionary<String, object> { { "stringProperty", "Testing" }, { "intProperty", 22 } });
}
|
Property Injection
| // If constructor injection isn't possible/preferable we can also
// use property injection.
//
// Any public read/write property that we can resolve, that isn't
// already set to a value, is resolved by the container:
class TestClassPropertyDependencies
{
public ITestInterface Property1 { get; set; } // Will be set if we can resolve and isn't already set
public ITestInterface2 Property2 { get; set; } // Will be set if we can resolve and isn't already set
public int Property3 { get; set; } // Will be ignored
public string Property4 { get; set; } // Will be ignored
public TestClassDefaultCtor ConcreteProperty { get; set; } // Will be set if we can resolve and isn't already set
public ITestInterface ReadOnlyProperty { get; private set; } // Will be ignored
public ITestInterface2 WriteOnlyProperty { internal get; set; } // Will be ignored (no way to know if it's already set)
}
// -- SNIP -- //
var input = new TestClassPropertyDependencies();
container.BuildUp(input); // Properties are now set
|
Child Containers
| // If we need to control scope or lifetime of particular
// registrations we can use child containers.
//
// We can get a child container from any container instance
var child = container.GetChildContainer();
// A child container will "bubble up" resolve requests if it can't resolve
// a type itself
container.Register<ITestInterface, TestClassDefaultCtor>();
var result = child.Resolve<ITestInterface>(); // Will resolve with the parent container
// If a type is registered with the child container it will resolve that
// instead of the same type registered with the parent:
container.Register<ITestInterface>(parentInstance);
child.Register<ITestInterface>(childInstance);
child.Resolve<ITestInterface>(); // Will return "childInstance"
// We can let the child container go out of scope because the
// parent holds no reference to it. Or we can Dipose() it which
// will DISPOSE ALL INSTANCES/SINGLETONS REGISTERED WITH IT if they
// support IDisposable:
child.Dispose(); // Disposes container and all disposable instances/singletons
|
This revision is from 2010-06-24 17:01