Tip:
Highlight text to annotate it
X
Hello, my name is Daniel Cazzulino
we're gonna go through
the process of creating a dependency injection framework
which is blazingly fast and the reason is..
for starting this from scratch rather than using
one of the many ones available
is that after some
initial testing I found out that lots of them
most of them are created with different scenarios in mind which include using
them on the desktop and
the one we are gonna be creating is gonna be
highly optimized
so that it can also run very fast on the Compact Framework [note: former mobile .NET OS]
which is a constraint execution environment where
every bit of IL that has to be JIT-ed has an impact on performance
so we're gonna focus
on how to do this in the most performing
way possible
and we're gonna do these in
true TDD fashion
so that you can also
see how the technique can be applied
on a rather interesting problem domain
so the first thing we're gonna do is going to be...
the code name for this project is gonna be Funq
but this is something that's gonna come out with the next
version of the Mobile Client Software Factory
and it'll probably be called something like Container Model or something like that
but let's call it Funq, which is fancier
so let's first create the two
projects here, we have
the library then
Funq.Tests for the tests project. Let's add a reference from
the test project to the main library one
let's get rid of the default classes there... and off we go
so, the first thing is
let's setup the very basic scenario
let's call it container fixture
because we'll probably have a class which is gonna be called Container
and before we get into how we're gonna solve this problem, let's just create a new
test class here
I don't know why it's not adding the reference to the proper
Visual Studio
unit testing framework... no worries on that
it's called Quality Tools... Unit Testing Framework
the reason I'm using the Visual Studio testing framework is that that's the one
that is supported by the
the only one that supports the Compact Framework natively
so
that's gonna come in handy when I have to port this for the Compact Framework
which is just gonna be changing the project types and all that
so, let's say
let's think about the problem
creating instances, right, of the objects
of course, let's say you have a very basic model here. Let's do a public interface
this is gonna be the typical nonsense public interface
IFoo, IBar
that we all love to write
of course I'll leave it to you to imagine a more useful scenario
Let's do the Bar, and the Foo
which implements
IFoo, and let's say this guy has a dependency on IBar
and the dependency is received in the constructor
and basically here in the constructor
we just go and set the bar, the local bar, ok?
the first thing is
of course the fastest way
of doing this is the way you do it typically, right?. You do bar equals new Bar, right? And...
ah, it's not Boo, it's Bar :). You declare a new bar, then you do a new Foo
and you pass in the Bar
and of course that's gonna be the fastest way possible in .NET to create objects
Now, the whole point of dependency injection is that
Foo shouldn't care
which Bar it's being passed in
and by the time you are
"newing up" a Foo
you might not want to
actually instantiate the whole graph but rather
externalize that to somebody
to take care for it
the basic idea is that
you move that responsibility to a container
and basically the container decides which instances you get
and that gives you flexibility in changing the implementations of those
dependencies
either at run time or at configuration time for your
application
and you don't have to change anything
in the behavior that depends on Foo
because you would be getting the Bar from... wherever
from the container typically
so uh... the first and the most typical way of
externalizing these
"new" keywords
is basically doing the
Activator.CreateInstance, right?
now
Activator.CreateInstance will get
first the
I think it's the
default constructor for the class
or the first constructor if there's only one [incorrect, it has to be a default constructor always]
in this case it will fail because we're trying to create an instance and we're not passing the Bar
so we would have to do the same thing
Activator.CreateInstance with the Bar
how we do that in a generic way
well, typically we will walk the
constructors in Foo
find out which is the one
that has the most arguments
and for each argument and we're gonna do the same
and then do Activator.CreateInstance on all of them
so that you get your object graph
Activator.CreateInstance has a very high
cost in terms of performance
in the Compact Framework
specifically
reflection is prohibitively expensive, so this is not an option
so the other option is that you can
go and say
I'm going to have a Bar factory
and basically specify that this is gonna be a lambda
now the cool thing of lambdas is that
this is not
executed
at this point, so we are not "newing up" a Bar
at this point, we're just declaring a delegate that we can invoke
and at that point this code will run
so when we do
bar2 equals bar factory
like this
we can call it as a method or do the Invoke
on it, which is the same thing
at that point this
delegate will run
and we will get a new instance
and you can do the same with the Foo of course
and because Foo needs a Bar, we can do barFactory.Invoke
to pass the Bar
so in this case we have
externalized the "new" of the Bar from here into a delegate
so you can see where this is going
the core idea is
there is a container that will be [registering] receiving registrations for
the factories
that create objects
and then it will take care of
hooking up the dependencies on those
objects and do that in a way which is
high-performing
so let's start in true TDD fashion and say
we can register
register type and get instance, right?
so what we want to do
as I mentioned we will probably have
a Container class somewhere, and we're gonna do
Container.Register IBar.
And what we're gonna register is the function that will do the
"new" of the Bar
next we want to
retrieve that Bar
from the container doing Container.Resolve IBar
we can assert that we didn't get a null and
it's true that the bar is indeed an instance
of the Bar concrete type
which is the thing we're "newing up" here, right?
So let's go and create the container class
Container is going to be
pretty straightforward at this point
we'll have a public void Register service, and we're registering
a Func of the TService
which we'll call the factory for that guy
now we need a way
to resolve it, so we'll have also a public
TService Resolve TService and that should
return something, for now let's return the default for a TService, and see what happens. Ok, so now our code will compile, of course.
But it will not pass
ok, because
we get a null, from here
so let's go and implement this, how do we return a non-null?
well first we'll need a dictionary to keep the factories
that maps the type of the service
to the factory, the corresponding factory
we'll use object there because there's no... this is a delegate, so there's no base class for
a generic Func, so we'll just have to cast it when we need it
and let's call this the factories. And now here we can do factories.Add
type of TService, and the factory
all right
and from here we can retrieve a factory from the dictionary... type of TService
there's some error handling that we might want to do later
we will need a test to fail before we can do that
so for now, we're going to say
return... cast the factory
to the type we know it is, because
this is the only way of registering a factory, so we don't need to check
that the actual type that is in that dictionary
matches because we known it does
so we cast
the factory and finally do the Invoke
which gives us the instance that we need
we come back here
we go and run
and now we failed, why? we're still getting a null
factory invoke... did I actually compile the dependent project? Assert...
oh, Is Null...Is *Not* Null is what
we should be testing for. There we go. Pass!
now...
let's get to
the other issue which is injecting the dependency in the constructor
for the Foo
so go let's create another test
and say... inject dependencies
registering
types and injecting
dependencies... so what we're
going to do here is
this is "registers"... this should be worded as... registering... registering
types gets
dependencies injected... Resolve gets dependencies
injected, that's better.. new Container
and now register the Bar, there
now we need to register the Foo
and here comes the thing
how do we get a Bar there?
well, ideally we would like to get the Bar from the container itself
and we can do this, container.Resolve
IBar
the problem with doing this is that here from within the function we are
referencing an object
which is defined outside, that is called the closure
so the compiler will have to generate a class that keeps that reference
so that it's available
when we invoke this delegate. We don't want that to happen
so typical
approach to this is to actually have
the function receive
an argument which is the container we need to use at that time
and we can do the same with the other one
so we will have to slightly modify
the register method
so that now instead of receiving a function
that just returns the type
it receives a function that can receive the
container as an argument
so this is gonna be a function of Container, coma
TService, so it's a function that receives the Container as an argument and
returns a service. So here we will need to cast accordingly... container, service. And basically in the invocation
we will pass ourselves
this allows the
delegate here to receive the instance of the container and
use that to resolve the other bar
we can actually set here a breakpoint so that you can actually
see that happening
if I press F9 now
Camtasia will go Pause
so we're not gonna do that
let's go and run this
next we need to Assert that
Is Not Null of, what? We need to return the Foo.
Foo equals container.Resolve of IFoo. And so
Foo is not null. Let's also get
the reference as a Foo
so that we can access
this property here, Bar
and let's Assert that Bar is not null
so that we did actually did get the dependency injected. Let's run this
tests results... pass!
so that's it, we have it
working there... I'm trying to think how to
set up a breakpoint there
let me see if I can change the... nah, let's see that later
so uh...
so basically this is the bare, absolutely bare minimum DI container that you can have. It's not very smart, it has only one
very
simple approach, it doesn't manage
the lifecycle of the
instances, like reusing them at the container level
it doesn't support
container hierarchies or
named services and the like
but we're gonna go
we're gonna get there
on the next screencast. That's all for this. It's pretty
like the bare bones, 15' container
that you can have. Thanks
and see you on the next one!