Skip to content

Compare: IoC Container

Showing with 41 additions and 34 deletions.
  1. +41 −34 IoC-Container.md
75 changes: 41 additions & 34 deletions IoC-Container.md
@@ -1,23 +1,23 @@
The _Rubberduck_ (_RD_) project uses an _Inversion of Control_ (_IoC_) container to facilitate its _dependency injection_ (_DI_) needs, in particular constructor injection and property injection for specific classes of objects. This means that IoC container is used to resolve the constructor parameters for most objects used in RD.
The _Rubberduck_ (_RD_) project uses an _Inversion of Control_ (_IoC_) container to facilitate its _dependency injection_ (_DI_) needs. In general, we use constructor injection. For specific classes of objects, we use property injection. This means that IoC container is used to resolve the constructor parameters for most objects used in RD.

An explicit request to resolve an object only appears in one place in RD only, in the so called _composition root_. There, the `App` object gets resolved. The remaining resolution either happens during recursively resolving this object or is deferred to automatically generated factories supplied by the IoC container.

The currently used IoC container in RD is _Castle Windsor_ (_CW_).

# General Setup #

Our IoC container has several useful registration features we use to ease the burdon of registering components in the IoC container. This includes _conventions_, which register entire categories of classes, and _automagic factories_, which allow to defer (repeated) resolution of objects to a later point in time than the resolution in the composition root. We also use the property injection capabilities to inject the commands into our view models.
Our IoC container has several useful registration features we use to ease the burden of registering components in the IoC container. This includes _conventions_, which register entire categories of classes, and _automagic factories_, which allow to defer (repeated) resolution of objects to a later point in time than the resolution in the composition root. We also use the property injection capabilities to inject the commands into our view models.

## Registrations by Convention ##

With a registration by convention, concrete classes get registered to interfaces based on general conditions. We currently use the following conventions:

- We register almost all concrete types to their default interface, in particular a class `Something` will automatically register to the interface `ISomething` if it exists. The resolution happens in transient scope, i.e. each user gets its own object.
- Code inspections must be implement `IInspection` or `IParseInspection` depending on the nature of the inspection. In both cases they automatically multi-register to `IInspection`, in transient scope. This means that resolving an `IEnumerable<IInspection>` will yield an enumerable containing all inspections.
- Quickfixes have to implement `IQuickFix` and are automatically multi-registered to this interface, in singleton scope, i.e. each requestor will get the same object.
- Auto complete handlers have to derive from `AutoCompleteHandlerBase` and are multi-registered to this class, in transient scope.
- Code metric providers have to derive from 'CodeMetric` and are multi-registered to this class, in singleton scope.
- All interfaces whose names end in `Factory` get registered as automatic factories implemented by the IoC container (see below), in singleton scope.
- We register almost all concrete types to their default interface. In particular, a class named `Something` will automatically register to the interface `ISomething` if it exists. The resolution happens in transient scope, i.e. each user gets its own object.
- Code inspections must implement `IInspection` or `IParseInspection` depending on the nature of the inspection. In both cases, they automatically multi-register to `IInspection`, in transient scope. This means that resolving an `IEnumerable<IInspection>` will yield an enumerable containing all inspections.
- Quickfixes have to implement `IQuickFix` and are automatically multi-registered to this interface in singleton scope, i.e. each requestor will get the same object.
- Auto complete handlers must derive from `AutoCompleteHandlerBase` and are multi-registered to this class in transient scope.
- Code metric providers must derive from 'CodeMetric` and are multi-registered to this class in singleton scope.
- All interfaces whose names end in `Factory` get registered as automatic factories implemented by the IoC container (see below) in singleton scope.

## Automagic Factories ##

Expand All @@ -31,21 +31,23 @@ public interface ISomeFactory : IDisposable
```
as a factory interface, CW will resolve the interface `ISomething` when `Create` is called. To construct the concrete type registered to `ISomething`, it will use the argument for the parameter `foo` for a constructor parameter of the same name of the concrete type's constructor. All other constructor arguments will be resolved by CW.

Note that such automagic factories should generally only be used with interfaced for which the registration is in transient scope, i.e. where every caller gets a new object. If the registration is in singleton scope, the second caller will get the same object as the first without any regard to the paramters he provided. This can cause very surprising behaviour.
*NOTE*: Do not implement the `ISomeFactory`; the implementation is provided by CW automatically. In some cases, we choose to implement the factories ourselves. In those scenarios, those factories are registered as ordinary resolved objects.

In the factory interface, multiple create methods of nearly any name can be mixed; `Create` is no special name. The only restriction is that the IoC container must be able to resolve the return type using the parameters provided.
Note that such automagic factories should generally only be used with interfaces for which the registration is in transient scope, i.e. where every caller gets a new object. If the registration is in singleton scope, the second caller will get the same object as the first without any regard to the parameters the user provided. This can cause very surprising behaviour.

For the create methods there is one special naming convention: a create method of the form `GetSoemthing` uses a named registration `Something` to resolve the return type. So, be cautious not to start the methods with `Get` unless you know what you are doing.
In the factory interface, multiple create methods of nearly any name can be mixed; `Create` is not a special name. CW only considers the method's signature in determining which is a creation method and which is a release method. The only restriction is that the IoC container must be able to resolve the return type using the parameters provided. If the parameters should come from CW itself, it can be omitted as CW will resolve that. But if the parameter itself is not resolvable using CW, you may get a runtime error failing to create the object.

Note that a each automagic factory will keep a reference to all objects it has resolved in order to be able to dispose them when the IoC container gets disposed. This means that without further action the lifetime of the resolved objects is at least as long as that of the factory. To tackle this problem, each `void` method on the interface gets implemented as a release method, i.e. resolved components passed to these methods get released from the container and disposed if they implement `IDisposable`. Here, the names of the methods are irrelevant, with the excpetions of `Dispose`.
For the create methods, there is one special naming convention: a create method of the form `GetSoemthing` uses a named registration `Something` to resolve the return type. So, be cautious not to start the methods with `Get` unless you know what you are doing.

If the factory interface implements `IDisposable`, a call to the `Dispose` method will dispose the factory, which triggers a release of all objectes resolved by the factory, including disposal if they implement `IDisposable` themselves.
Note that each automagic factory will keep a reference to all objects it has resolved in order to be able to dispose them when the IoC container gets disposed. This means that without further action the lifetime of the resolved objects is at least as long as that of the factory. To tackle this problem, each `void` method on the interface gets implemented as a release method, i.e. resolved components passed to these methods get released from the container and disposed if they implement `IDisposable`. Here, the names of the methods are irrelevant, with the exception of `Dispose`.

If the factory interface implements `IDisposable`, a call to the `Dispose` method will dispose the factory, which triggers a release of all objects resolved by the factory, including disposal if they implement `IDisposable` themselves.

For more information refer to the [documentation](https://github.com/castleproject/Windsor/blob/master/docs/typed-factory-facility-interface-based.md).

### Deferred Resolution via Factories ###

The automagic factories combined with the type `Lazy<T>` allow to defer the resolution of heavy-weight components to the time when they are used the first time. To do this the factory gets constructor injected instead of the component in order to use its create method as the construction delegate for the `Lazy<T>`.
The automagic factories combined with the type `Lazy<T>` allow to defer the resolution of heavy-weight components to the time when they are used the first time. To do this the factory should be constructor injected instead of the component in order to use its create method as the construction delegate for the `Lazy<T>`.

Suppose we have the following class using an `IHeavyComponent`.
```csharp
Expand Down Expand Up @@ -99,16 +101,19 @@ public class Foo : IDisposable

public void Dispose()
{
_heavyComponentFactory.Release(_heavy.Value)
if(_heavy.IsValueCreated)
{
_heavyComponentFactory.Release(_heavy.Value)
}
}
}
```

## Property Injection ##

Our IoC container allows to inject values into settable properties upon reolution. In genreal, this causes more problems than it solves, because there are a lot of classes with settable properties not intended to be filled upon consttuction. Consquently, we limit the scope of property injection. More precisely, properties only get injected into classes deriving from `ViewModelBase` and only properties for commands, i.e. classes deriving from `CommandBase`, get injected.
Our IoC container allows us to inject values into settable properties upon resolution. In general, this causes more problems than it solves, because there are a lot of classes with settable properties not intended to be filled upon construction. Consequently, we limit the scope of property injection. More precisely, properties only get injected into classes deriving from `ViewModelBase` and `CommandBase`.

The extend of property injection is governed by the `RubberduckPropertiesInspector`.
The scope of property injection is governed by the `RubberduckPropertiesInspector`.


# Component Registration #
Expand All @@ -123,18 +128,18 @@ Before providing examples what the actual setup code in CW looks like, there are

### Scopes/Lifestyles ###

In CW, component registrations can have different _lifestyles_, in other IoC containers often called _scopes_. These determine how many instances are generated for a registration. There are quite a few lifestyles, but for RD, only two are really relevant.
In CW, component registrations can have different _lifestyles_. Note that in other IoC containers, those are often called _scopes_. These determine how many instances are generated for a registration. There are quite a few lifestyles, but for RD, only two are really relevant.

For registrations in transient lifestyle, each request gets its own instance of the concrete clas registered to the interface requested. For interfaces resolved by factories, this is usually the right scope. Generally, this lifestyle should be chosen if each user needs a separate set of data on its instance.
For registrations in transient lifestyle, each request gets its own instance of the concrete class registered to the interface requested. For interfaces resolved by factories, this is usually the right scope. Generally, this lifestyle should be chosen if each user needs a separate set of data on its instance.

For registrations in singleton scope, each request gets the sane instance of the concrete clas registered to the interface requested. There are two main reasons to use this lifestyle. First, objects that form a global repository or function as a global hub for specific interactions like the `RubberduckParserState` or the `IRewritingManager` require this lifestyle to ensure that everybody uses the same data and the same access point to specific functionality. Second, this scope can ease the memory burdon for concrete classes without state. However, any dependency of the object will remain in memory until the container gets disposed.
For registrations in singleton scope, each request gets the sane instance of the concrete class registered to the interface requested. There are two main reasons to use this lifestyle. First, objects that form a global repository or function as a global hub for specific interactions like the `RubberduckParserState` or the `IRewritingManager` require this lifestyle to ensure that everybody uses the same data and the same access point to specific functionality. Second, this scope can ease the memory burden for concrete classes without state. However, any dependency of the object will remain in memory until the container gets disposed.

In contrast to most other IoC containers, in CW singleton is the default lifestyle. Since this is not obvious, in RD it is preferred to always state the lifestyle explicitly.


### There can Only be One Registration ###

In CW, registrations belong to the implementing (concrete) type, not the interface. This is important to realize because CW does not support multiple registrations of the same (implementing) type. If you add a second registration, CW will issue an error at runtime. (This is one of the rasons you should always test whether RD actually loads after changing registrations.)
In CW, registrations belong to the implementing (concrete) type, not the interface. This is important to realize because CW does not support multiple registrations of the same (implementing) type. If you add a second registration, CW will issue an error at runtime. This is one of the reasons you should always test whether RD actually loads after changing registrations.

Generally, CW will not complain if a registration by convention covers an implementing type already registered; it simply ignores it in the convention. However, should a convention pick up an implementing type that gets registered via an individual registration later, the later registration will throw. Consequently, registrations by convention should come after individual registrations.

Expand Down Expand Up @@ -174,7 +179,7 @@ container.Register(Component.For<VBAPredefinedCompilationConstants>()
.LifestyleSingleton());
```

To overwrite the registration for a type in this registration, you can use `OnComponent<TInterface, TImplementing>`:
To specify a dependent type, you can use `OnComponent<TInterface, TImplementing>`:

```csharp
container.Register(Component.For<VBAPreprocessorParser>()
Expand All @@ -183,7 +188,7 @@ container.Register(Component.For<VBAPreprocessorParser>()
.LifestyleSingleton());
```

To override the the resitration for specific constructor prarmeters by name, you can use `OnComponent(string paramName, Type implementingType)`:
If you need to use a specific constructor for the type, you can use `OnComponent(string paramName, Type implementingType)`:

```csharp
container.Register(Component.For<IModuleParser>()
Expand All @@ -196,13 +201,13 @@ container.Register(Component.For<IModuleParser>()

## Registration of Already Existing Instances ##

The CW IoC container allows to register already existing instances as implementations of a type. These will always act as registerd in singleton scope, no matter which lifestyle gets specified. (It is always the concrete instance you provided that gets returned.)
The CW IoC container allows to register already existing instances as implementations of a type. These will always act as registered in singleton scope, no matter which lifestyle gets specified. (It is always the concrete instance you provided that gets returned.)

```csharp
container.Register(Component.For<IVBE>().Instance(_vbe));
```

We only use this kind of registration to register the top-level COM objects handed to us on startup or immediately accuired from these. These cannot be generated by CW.
We only use this kind of registration to register the top-level COM objects handed to us on startup or immediately acquired from these. These cannot be generated by CW.

## Registration by Convention ##

Expand All @@ -226,13 +231,13 @@ container.Register(Classes.FromAssembly(assembly)

### Specifying the Implementing Types ###

A registration by convention always starts with a specification of the implementing types for which to add a registration. Usually we use `Classes.FromAssembly(assembly)` or `Types.FromAssembly(assembly)` since we have multiple assemblies to load from. The difference between the version with `Classes` and with `Types` is that the formare only considers concrete classes wherease the latter also considers interfaces and abstract classes.
A registration by convention always starts with a specification of the implementing types for which to add a registration. Usually we use `Classes.FromAssembly(assembly)` or `Types.FromAssembly(assembly)` since we have multiple assemblies to load from. The difference between the version with `Classes` and with `Types` is that the former only considers concrete classes whereas the latter also considers interfaces and abstract classes.

We always add `IncludeNonPublicTypes` to enable registration of internal types in other assemblies. This allows us to make interfaces only used inside one project internal to remove any posible usage from outside.
We always add `IncludeNonPublicTypes` to enable registration of internal types in other assemblies. This allows us to make interfaces only used inside one project internal to remove any possible usage from outside the solution.

Next follows a restirction of the implementing types to register. We use two variants, the one using a prdicate via the `Where` method and one using `BasedOn<TInterface>`. The latter filters for implementations of the interface.
Next follows a restriction of the implementing types to register. We use two variants, the one using a predicate via the `Where` method and one using `BasedOn<TInterface>`. The latter filters for implementations of the interface.

It is important to note that applying both `Where` and `BasedOn<T>` yields a _union_ of the filter results and _not the intersection_ of the results. In order to specify further a registration using `BasedOn<TInterface>` one has to use either `If` of `Unless`, which have the implied meaning.
It is important to note that applying both `Where` and `BasedOn<T>` yields a _union_ of the filter results and _not the intersection_ of the results. In order to specify further a registration using `BasedOn<TInterface>` one has to use either `If` or `Unless`, which have the implied meaning.

```csharp
container.Register(Classes.FromAssembly(assembly)
Expand All @@ -245,9 +250,9 @@ container.Register(Classes.FromAssembly(assembly)

### Specifying the Types to be Implemented ###

The next part of convention is the type to be implemented, the _service_. There are several possibilities, two of which can be seen in the examples above. The version with `Base()` registers to the generic type in the call to `BasedOn<T>`, which has to be present for this to make sense. The version with `DefaultInterfaces()` registers a concrete implmentaton `ConcreteName` for all interfaces `IInterfaceName` such that the name `ConcreteName` contains `InterfaceName`. (Note the missing _I_.)
The next part of convention is the type to be implemented, the _service_. There are several possibilities, two of which can be seen in the examples above. The version with `Base()` registers to the generic type in the call to `BasedOn<T>`, which has to be present for this to make sense. The version with `DefaultInterfaces()` registers interfaces to concrete types based on the names -- if the interface is named `IFoo`, it will connect to any concrete types named `Foo`, and that will be returned.

It is also possible to specify the types to be implmented explicitely using `Select(Type[] types)`.
It is also possible to specify the types to be implemented explicitly using `Select(Type[] types)`.
```csharp
container.Register(Classes.FromAssembly(assembly)
.IncludeNonPublicTypes()
Expand All @@ -259,11 +264,11 @@ container.Register(Classes.FromAssembly(assembly)

Further service specifications are `AllInterfaces()` and `Self()`, which do the obvious thing.

### Specifying Futher Configuaration ###
### Specifying Further Configuration ###

Usually, the convention ends with the specification of the lifestyle of the registrations. However, there are two further clauses we use to modify the convention, `OnCreate` and `Configure`.

The `OnCreate` method allows to perform actions right after createion of the implementing object. This can be used to perfor property injection.
The `OnCreate` method allows to perform actions right after creation of the implementing object. This can be used to perform property injection.
```csharp
container.Register(Component.For<IParentMenuItem>()
.ImplementedBy<TMenu>()
Expand Down Expand Up @@ -292,4 +297,6 @@ container.Register(Types.FromAssembly(assembly)
.WithService.Self()
.Configure(c => c.AsFactory())
.LifestyleSingleton());
```
```

As noted previously; those should be interfaces without a corresponding implementation. CW will provide the implementation. In case where we use concrete factories, those should be explicitly registered as an ordinary object without the `AsFactory()`.