Shake up how autobinding is done in StyletIoC

Instead of creating tons of registrations for all discovered types,
instead create those registrations as and when they are requested.

This means we don't flood the CLR's type cache with RuntimeTypes which
are never used but can never be collected.
This commit is contained in:
Antony Male 2015-03-15 11:46:32 +00:00
parent a5f642d95c
commit 9b1b1308b6
2 changed files with 40 additions and 12 deletions

View File

@ -1,4 +1,5 @@
using StyletIoC.Creation;
using StyletIoC.Internal.Creators;
using StyletIoC.Internal.RegistrationCollections;
using StyletIoC.Internal.Registrations;
using System;
@ -18,6 +19,8 @@ namespace StyletIoC.Internal
// ReSharper disable once RedundantExtendsListEntry
internal class Container : IContainer, IRegistrationContext
{
private readonly List<Assembly> autobindAssemblies;
/// <summary>
/// Maps a [type, key] pair to a collection of registrations for that keypair. You can retrieve an instance of the type from the registration
/// </summary>
@ -53,6 +56,11 @@ namespace StyletIoC.Internal
private bool disposed;
public Container(List<Assembly> autobindAssemblies)
{
this.autobindAssemblies = autobindAssemblies ?? new List<Assembly>();
}
/// <summary>
/// Compile all known bindings (which would otherwise be compiled when needed), checking the dependency graph for consistency
/// </summary>
@ -161,7 +169,8 @@ namespace StyletIoC.Internal
if (this.registrations.TryGetValue(typeKey, out registrations) ||
this.TryCreateFuncFactory(typeKey, out registrations) ||
this.TryCreateGenericTypesForUnboundGeneric(typeKey, out registrations))
this.TryCreateGenericTypesForUnboundGeneric(typeKey, out registrations) ||
this.TryCreateSelfBinding(typeKey, out registrations))
{
return true;
}
@ -222,6 +231,27 @@ namespace StyletIoC.Internal
return this.TryRetrieveGetAllRegistrationFromElementType(new TypeKey(elementType, typeKey.Key), typeKey.Type, out registration);
}
private bool TryCreateSelfBinding(TypeKey typeKey, out IRegistrationCollection registrations)
{
registrations = null;
if (typeKey.Type.IsAbstract || !typeKey.Type.IsClass)
return false;
var injectAttribute = typeKey.Type.GetCustomAttribute<InjectAttribute>(true);
if (injectAttribute != null && injectAttribute.Key != typeKey.Key)
return false;
// Only allow types in our whitelisted assemblies
// This stops us trying to charge off and create List<T> or some other BCL class which we don't have a hope in hell of creating
// This in turn leads to some very hard-to-debug error cases where we descend into infinite recursion on some random type
if (!this.autobindAssemblies.Contains(typeKey.Type.Assembly))
return false;
registrations = this.AddRegistration(typeKey, new TransientRegistration(new TypeCreator(typeKey.Type, this)));
return true;
}
/// <summary>
/// If the given type is a Func{T} or a Func{string, T}, get a registration which can create an instance of it
/// </summary>
@ -325,7 +355,8 @@ namespace StyletIoC.Internal
// Try to get registrations. If there are none, see if we can add some from unbound generics
if (this.registrations.TryGetValue(typeKey, out registrations) ||
this.TryCreateFuncFactory(typeKey, out registrations) ||
this.TryCreateGenericTypesForUnboundGeneric(typeKey, out registrations))
this.TryCreateGenericTypesForUnboundGeneric(typeKey, out registrations) ||
this.TryCreateSelfBinding(typeKey, out registrations))
{
readOnlyRegistrations = registrations;
}

View File

@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace StyletIoC
{
@ -203,12 +204,13 @@ namespace StyletIoC
public class StyletIoCBuilder : IStyletIoCBuilder
{
private readonly List<BuilderBindTo> bindings = new List<BuilderBindTo>();
private List<Assembly> autobindAssemblies;
/// <summary>
/// Gets or sets the list of assemblies searched by Autobind and ToAllImplementatinos
/// </summary>
public List<Assembly> Assemblies { get; set; }
/// <summary>
/// Initialises a new instance of the <see cref="StyletIoCBuilder"/> class
/// </summary>
@ -254,14 +256,9 @@ namespace StyletIoC
/// <param name="assemblies">Assemblies to search, in addition to the Assemblies property</param>
public void Autobind(IEnumerable<Assembly> assemblies)
{
// We self-bind concrete classes only
var classes = this.GetAssemblies(assemblies, "Autobind").SelectMany(x => x.GetTypes()).Where(c => c.IsClass && !c.IsAbstract);
foreach (var cls in classes)
{
// It's not actually possible for this to fail with a StyletIoCRegistrationException (at least currently)
// It's a self-binding, and those are always safe (at this stage - it could fall over when the containing's actually build)
this.Bind(cls).To(cls).AsWeakBinding();
}
// If they've called Autobind before, then add the new set of assemblies on
var existing = this.autobindAssemblies ?? Enumerable.Empty<Assembly>();
this.autobindAssemblies = existing.Concat(this.GetAssemblies(assemblies, "Autobind")).Distinct().ToList();
}
/// <summary>
@ -300,7 +297,7 @@ namespace StyletIoC
/// <returns>An IContainer, which should be used from now on</returns>
public IContainer BuildContainer()
{
var container = new Container();
var container = new Container(this.autobindAssemblies);
// Just in case they want it
this.Bind<IContainer>().ToInstance(container).AsWeakBinding();