I have been using Microsoft.Unity
as my container and have decided that the approach for a lazy implementation causes too much rework. Each time you decide to swap out to use a lazy you have to go add .Value
or a .Resolve()
after your variable (which could be called hundreds of times).
Suggested lazy implementation by microsoft Deferring the Resolution of Objects
So I decided to start from scratch.
Firstly I needed an ILazy
interface (I did add the Value
property so you can get the actual value if it is ever needed)
public interface ILazy<out TInterface> where TInterface : class
{
TInterface Value { get; }
}
We then need an interface and a type that is both ILazy<T>
and of the interface to be implemented.
public interface IContract
{
//methods and properties to be implemented
void SomeFunction();
}
public interface ILazyContract : ILazy<IContract>, IContract
{
}
public class ContractImplementation : IContract
{
public void SomeFunction()
{
Console.WriteLine("Doing something");
}
}
I now need a factory which creates the implementation of the ILazyContract
at runtime. This is a long class and has a bit of il generation so it might be difficult to read.
public class LazyTypeFactory
{
//used to stop the same class being created twice
private readonly object _lock = new object();
private readonly Dictionary<Type, Type> _lazyTypes = new Dictionary<Type, Type>();
private readonly ModuleBuilder _moduleBuilder;
public LazyTypeFactory(string assembly, string module)
{
var an = new AssemblyName(assembly);
var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
_moduleBuilder = assemblyBuilder.DefineDynamicModule(module);
}
public Type CreateOrGetLazyType<TLazy, TInterface>(string name = null)
where TLazy : ILazy<TInterface>, TInterface
where TInterface : class
{
Type result;
var @interface = typeof (TInterface);
var @container = typeof (IUnityContainer);
var @lazy = typeof (TLazy);
var @func = typeof (Func<IUnityContainer, TInterface>);
name = name ?? @lazy.Name + "Impl";
name = @lazy.Namespace + "." + name;
if ([email protected])
{
throw new Exception("Expected TInterface to be a type of interface");
}
lock (_lock)
{
if (_lazyTypes.ContainsKey(@lazy))
{
result = _lazyTypes[@lazy];
}
else
{
var typeBuilder = _moduleBuilder.DefineType(name, TypeAttributes.Public | TypeAttributes.Class, null);
typeBuilder.AddInterfaceImplementation(@lazy);
//private field for container
var c = typeBuilder.DefineField("_c", @container, FieldAttributes.Public);
//private field for func
var f = typeBuilder.DefineField("_f", @func, FieldAttributes.Public);
//private field for func result
var v = typeBuilder.DefineField("_v", @interface, FieldAttributes.Public);
//constructor with func
CreateConstructor(typeBuilder, @container, c, @func, f);
//private property to get func result
var propertyBuilder = CreateValueProperty(typeBuilder, @interface, v, @container, c, @func, f);
//interface methods
//call prop then invok method
foreach (var iMethod in @interface.GetMethods())
{
var mb = CreateOverride(typeBuilder, propertyBuilder, iMethod);
typeBuilder.DefineMethodOverride(mb, iMethod);
}
foreach (var iProperty in @interface.GetProperties())
{
var pb = typeBuilder.DefineProperty(iProperty.Name, PropertyAttributes.None, iProperty.PropertyType, Type.EmptyTypes);
if (iProperty.GetMethod != null)
{
var iMethod = iProperty.GetMethod;
var mb = CreateOverride(typeBuilder, propertyBuilder, iMethod);
pb.SetGetMethod(mb);
}
if (iProperty.SetMethod != null)
{
var iMethod = iProperty.SetMethod;
var mb = CreateOverride(typeBuilder, propertyBuilder, iMethod);
pb.SetSetMethod(mb);
}
}
_lazyTypes[@lazy] = result = typeBuilder.CreateType();
}
}
return result;
}
private static void CreateConstructor(TypeBuilder typeBuilder, Type @container, FieldBuilder c, Type @func, FieldBuilder f)
{
var constructor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new[] { @container, @func });
constructor.DefineParameter(1, ParameterAttributes.None, "container");
constructor.DefineParameter(2, ParameterAttributes.None, "func");
var cIl = constructor.GetILGenerator();
cIl.Emit(OpCodes.Ldarg_0);
cIl.Emit(OpCodes.Ldarg_1);
cIl.Emit(OpCodes.Stfld, c);
cIl.Emit(OpCodes.Ldarg_0);
cIl.Emit(OpCodes.Ldarg_2);
cIl.Emit(OpCodes.Stfld, f);
cIl.Emit(OpCodes.Ret);
}
private static PropertyBuilder CreateValueProperty(TypeBuilder typeBuilder, Type @interface, FieldBuilder v, Type @container, FieldBuilder c, Type @func, FieldBuilder f)
{
var propertyBuilder = typeBuilder.DefineProperty("Value", PropertyAttributes.None, @interface, Type.EmptyTypes);
var get = typeBuilder.DefineMethod("get_Value", MethodAttributes.Public | MethodAttributes.Virtual, @interface, new Type[0]);
var getIl = get.GetILGenerator();
var skip = getIl.DefineLabel();
getIl.Emit(OpCodes.Ldarg_0);
getIl.Emit(OpCodes.Ldfld, v);
getIl.Emit(OpCodes.Ldnull);
getIl.Emit(OpCodes.Bne_Un, skip);
//call _f and set to _v
getIl.Emit(OpCodes.Ldarg_0);
getIl.Emit(OpCodes.Ldarg_0);
getIl.Emit(OpCodes.Ldfld, f);
getIl.Emit(OpCodes.Ldarg_0);
getIl.Emit(OpCodes.Ldfld, c);
getIl.Emit(OpCodes.Call, @func.GetMethod("Invoke", new[] { @container }));
getIl.Emit(OpCodes.Stfld, v);
getIl.MarkLabel(skip);
getIl.Emit(OpCodes.Ldarg_0);
getIl.Emit(OpCodes.Ldfld, v);
getIl.Emit(OpCodes.Ret);
propertyBuilder.SetGetMethod(get);
return propertyBuilder;
}
private static MethodBuilder CreateOverride(TypeBuilder typeBuilder, PropertyBuilder propertyBuilder, MethodInfo iMethod)
{
var mb = typeBuilder.DefineMethod(iMethod.Name, MethodAttributes.Public | MethodAttributes.Virtual,
iMethod.ReturnType, iMethod.GetParameters().Select(x => x.ParameterType).ToArray());
var mIL = mb.GetILGenerator();
mIL.Emit(OpCodes.Ldarg_0);
mIL.Emit(OpCodes.Call, propertyBuilder.GetMethod);
for (var i = 0; i < iMethod.GetParameters().Length; i++)
{
mIL.Emit(OpCodes.Ldarg, i + 1);
}
mIL.Emit(OpCodes.Call, iMethod);
mIL.Emit(OpCodes.Ret);
return mb;
}
}
To create a Type
of ILazyContract
at runtime you would do something like this.
var factory = new LazyTypeFactory("Assembly.Lazy", "Module.Lazy");
var type = factory.CreateOrGetLazyType<ILazyContract, IContract>();
The implementation of this would look something like
public class ILazyContractImpl : ILazyContract
{
private readonly IUnityContainer _container;
private readonly Func<IUnityContainer, IContract> _func;
private IContract _value;
public ILazyContractImpl(IUnityContainer container, Func<IUnityContainer, IContract> func)
{
_container = container;
_func = func;
}
public IContract Value
{
get
{
if (_value == null)
{
_value = _func(_container);
}
return _value;
}
}
public void SomeFunction()
{
Value.SomeFunction();
}
}
As for the final part of the puzzle you now have to register this in the unity container. For this we also need to either register a Func<IUnityContainer, IContract>
or add it as a parameter in a InjectionConstructor
which is what I have done.
IUnityContainer container = new UnityContainer();
var factory = new LazyTypeFactory("Assembly.Lazy", "Module.Lazy");
var type = factory.CreateOrGetLazyType<ILazyContract, IContract>();
container.RegisterType<IContract, ContractImplementation>();
var resolve = new Func<IUnityContainer, IContract>(c => c.Resolve<IContract>());
container.RegisterType(typeof(ILazyContract), type, new InjectionConstructor(typeof(IUnityContainer), resolve));
var lazy = container.Resolve<ILazyContract>();
lazy.SomeFunction();
So there is my take on lazy - which wasn't very lazy :(.
Any feedback would be greatly appreciated, also if there are any improvements that can be made please let me know.
As requested by Heslacher, this is a real implementation where I wanted to cache the UserSetting
s because they will not change over the life span of a request.
Here you can see that the constructor will make a call to the db, which could take some time. So instead I would like to only do this when the class is actually used, not when it is created.
public interface IUserConfigurationService
{
UserSetting[] GetSettings();
UserSetting[] GetSettingsForProduct(Product product);
UserSetting[] GetSettingsOfType(SettingType type);
}
public class UserConfigurationService : IUserConfigurationService
{
private readonly Entities _entities;
private readonly User _currentUser;
//a cache so that we do not have to make the same call many times
private readonly UserSetting[] _userSettings;
public UserConfigurationService(Entities entities, User currentUser)
{
_entities = entities;
_currentUser = currentUser;
_userSettings = Entities.UserSettings.Where(x => x.User == currentUser).ToArray();
}
public UserSetting[] GetSettings()
{
return _userSettings;
}
public UserSetting[] GetSettingsForProduct(Product product)
{
return _userSettings.Where(x => x.Product == product).ToArray();
}
public UserSetting[] GetSettingsOfType(SettingType type)
{
return _userSettings.Where(x => x.SettingType == type).ToArray();
}
}
IEnumerable<UserSetting>
for the current user, or a factory which might use caching. – RobH Dec 9 '15 at 12:40