- fix passed null in factories

- fix ctors without params not checking for remaining args
ImplementExpressionTrees
Simon G 3 years ago
parent 54f06b6ebc
commit 2c57421e21
  1. 2
      LightweightIocContainer.Validation/IocValidator.cs
  2. 35
      LightweightIocContainer/Factories/FactoryHelper.cs
  3. 37
      LightweightIocContainer/Factories/TypedFactory.cs
  4. 16
      LightweightIocContainer/IocContainer.cs
  5. 30
      LightweightIocContainer/LightweightIocContainer.xml
  6. 22
      LightweightIocContainer/NullParameter.cs
  7. 57
      Test.LightweightIocContainer.Validation/IocValidatorTest.cs
  8. 84
      Test.LightweightIocContainer/FluentFactoryRegistrationTest.cs

@ -96,7 +96,7 @@ public class IocValidator
}
catch (Exception)
{
return null;
return new NullParameter(type);
}
}
}

@ -0,0 +1,35 @@
// Author: Gockner, Simon
// Created: 2022-09-02
// Copyright(c) 2022 SimonG. All Rights Reserved.
using System.Reflection;
namespace LightweightIocContainer.Factories;
/// <summary>
/// Helper class for the <see cref="TypedFactory{TFactory}"/>
/// </summary>
public class FactoryHelper
{
/// <summary>
/// Convert `null` passed as argument to <see cref="NullParameter"/> to handle it correctly
/// </summary>
/// <param name="createMethod">The create method of the factory</param>
/// <param name="arguments">The arguments passed to the create method</param>
/// <returns>List of arguments with converted null</returns>
/// <exception cref="Exception">Wrong parameters passed</exception>
public object?[] ConvertPassedNull(MethodBase createMethod, params object?[] arguments)
{
if (!arguments.Any())
return arguments;
ParameterInfo[] parameters = createMethod.GetParameters();
if (arguments.Length != parameters.Length)
throw new Exception("Wrong parameters passed");
for (int i = 0; i < arguments.Length; i++)
arguments[i] ??= new NullParameter(parameters[i].ParameterType);
return arguments;
}
}

@ -37,22 +37,28 @@ public class TypedFactory<TFactory> : TypedFactoryBase<TFactory>, ITypedFactory<
private TFactory CreateFactory(IocContainer container)
{
Type factoryType = typeof(TFactory);
AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("Factory"), AssemblyBuilderAccess.Run);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("Factory");
TypeBuilder typeBuilder = moduleBuilder.DefineType($"TypedFactory.{factoryType.Name}");
typeBuilder.AddInterfaceImplementation(factoryType);
//add `private readonly IIocContainer _container` field
//add `private readonly IocContainer _container` field
FieldBuilder containerFieldBuilder = typeBuilder.DefineField("_container", typeof(IocContainer), FieldAttributes.Private | FieldAttributes.InitOnly);
//add `private readonly FactoryHelper _helper` field
FieldBuilder helperFieldBuilder = typeBuilder.DefineField("_helper", typeof(FactoryHelper), FieldAttributes.Private | FieldAttributes.InitOnly);
//add ctor
ConstructorBuilder constructorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, new[] {typeof(IocContainer)});
ConstructorBuilder constructorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, new[] {typeof(IocContainer), typeof(FactoryHelper)});
ILGenerator constructorGenerator = constructorBuilder.GetILGenerator();
constructorGenerator.Emit(OpCodes.Ldarg_0);
constructorGenerator.Emit(OpCodes.Ldarg_1);
constructorGenerator.Emit(OpCodes.Stfld, containerFieldBuilder); //set `_container` field
constructorGenerator.Emit(OpCodes.Ldarg_0);
constructorGenerator.Emit(OpCodes.Ldarg_2);
constructorGenerator.Emit(OpCodes.Stfld, helperFieldBuilder); //set `_helper` field
constructorGenerator.Emit(OpCodes.Ret);
foreach (MethodInfo createMethod in CreateMethods)
@ -60,7 +66,9 @@ public class TypedFactory<TFactory> : TypedFactoryBase<TFactory>, ITypedFactory<
//create a method that looks like this
//public `createMethod.ReturnType` Create(`createMethod.GetParameters()`)
//{
// return IIocContainer.Resolve(`createMethod.ReturnType`, params);
// createMethod = MethodBase.GetCurrentMethod();
// object[] args = _helper.ConvertPassedNull(createMethod, params);
// return IocContainer.Resolve(`createMethod.ReturnType`, args);
//}
ParameterInfo[] args = createMethod.GetParameters();
@ -70,9 +78,16 @@ public class TypedFactory<TFactory> : TypedFactoryBase<TFactory>, ITypedFactory<
typeBuilder.DefineMethodOverride(methodBuilder, createMethod);
ILGenerator generator = methodBuilder.GetILGenerator();
generator.DeclareLocal(typeof(MethodBase));
generator.DeclareLocal(typeof(object[]));
generator.EmitCall(OpCodes.Call, typeof(MethodBase).GetMethod(nameof(MethodBase.GetCurrentMethod))!, null);
generator.Emit(OpCodes.Stloc_0);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldfld, containerFieldBuilder);
generator.Emit(OpCodes.Ldfld, helperFieldBuilder);
generator.Emit(OpCodes.Ldloc_0);
if (args.Any())
{
@ -93,8 +108,14 @@ public class TypedFactory<TFactory> : TypedFactoryBase<TFactory>, ITypedFactory<
MethodInfo emptyArray = typeof(Array).GetMethod(nameof(Array.Empty))!.MakeGenericMethod(typeof(object));
generator.EmitCall(OpCodes.Call, emptyArray, null);
}
generator.EmitCall(OpCodes.Call, typeof(FactoryHelper).GetMethod(nameof(FactoryHelper.ConvertPassedNull), new[] { typeof(MethodBase), typeof(object?[]) })!, null);
generator.Emit(OpCodes.Stloc_1);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldfld, containerFieldBuilder);
generator.Emit(OpCodes.Ldloc_1);
generator.EmitCall(OpCodes.Call, typeof(IocContainer).GetMethod(nameof(IocContainer.FactoryResolve), new[] { typeof(object[]) })!.MakeGenericMethod(createMethod.ReturnType), null);
generator.EmitCall(OpCodes.Call, typeof(IocContainer).GetMethod(nameof(IocContainer.FactoryResolve), new[] { typeof(object?[]) })!.MakeGenericMethod(createMethod.ReturnType), null);
generator.Emit(OpCodes.Castclass, createMethod.ReturnType);
generator.Emit(OpCodes.Ret);
}
@ -106,7 +127,7 @@ public class TypedFactory<TFactory> : TypedFactoryBase<TFactory>, ITypedFactory<
//create a method that looks like this
//public void ClearMultitonInstance<typeToClear>()
//{
// IIocContainer.ClearMultitonInstances<typeToClear>();
// IocContainer.ClearMultitonInstances<typeToClear>();
//}
if (multitonClearMethod.IsGenericMethod)
@ -134,6 +155,6 @@ public class TypedFactory<TFactory> : TypedFactoryBase<TFactory>, ITypedFactory<
}
}
return Creator.CreateInstance<TFactory>(typeBuilder.CreateTypeInfo()!.AsType(), container);
return Creator.CreateInstance<TFactory>(typeBuilder.CreateTypeInfo()!.AsType(), container, new FactoryHelper());
}
}

@ -117,7 +117,7 @@ public class IocContainer : IIocContainer, IIocResolver
/// <typeparam name="T">The given <see cref="Type"/></typeparam>
/// <param name="arguments">The constructor arguments</param>
/// <returns>An instance of the given <see cref="Type"/></returns>
public T FactoryResolve<T>(params object[] arguments) => ResolveInternal<T>(arguments, null, true);
public T FactoryResolve<T>(params object?[] arguments) => ResolveInternal<T>(arguments, null, true);
/// <summary>
/// Gets an instance of a given registered <see cref="Type"/>
@ -127,7 +127,7 @@ public class IocContainer : IIocContainer, IIocResolver
/// <param name="resolveStack">The current resolve stack</param>
/// <param name="isFactoryResolve">True if resolve is called from factory, false (default) if not</param>
/// <returns>An instance of the given registered <see cref="Type"/></returns>
private T ResolveInternal<T>(object[]? arguments, List<Type>? resolveStack = null, bool isFactoryResolve = false) => ResolveInstance<T>(TryResolve<T>(arguments, resolveStack, isFactoryResolve));
private T ResolveInternal<T>(object?[]? arguments, List<Type>? resolveStack = null, bool isFactoryResolve = false) => ResolveInstance<T>(TryResolve<T>(arguments, resolveStack, isFactoryResolve));
/// <summary>
/// Tries to resolve the given <see cref="Type"/> with the given arguments
@ -484,8 +484,6 @@ public class IocContainer : IIocContainer, IIocResolver
private (bool result, List<object?>? parameters, List<ConstructorNotMatchingException>? exceptions) TryGetConstructorResolveStack(ConstructorInfo constructor, object?[]? arguments, List<Type> resolveStack)
{
List<ParameterInfo> constructorParameters = constructor.GetParameters().ToList();
if (!constructorParameters.Any())
return (true, null, null);
List<ConstructorNotMatchingException> exceptions = new();
List<object?> parameters = new();
@ -500,10 +498,15 @@ public class IocContainer : IIocContainer, IIocResolver
if (passedArguments != null)
{
fittingArgument = passedArguments.FirstOrGiven<object?, InternalResolvePlaceholder>(a =>
a?.GetType() == parameter.ParameterType || parameter.ParameterType.IsInstanceOfType(a));
a?.GetType() == parameter.ParameterType ||
parameter.ParameterType.IsInstanceOfType(a) ||
a is NullParameter nullParameter && parameter.ParameterType.IsAssignableFrom(nullParameter.ParameterType));
if (fittingArgument is not InternalResolvePlaceholder)
passedArguments.Remove(fittingArgument);
if (fittingArgument is NullParameter)
fittingArgument = null;
}
if (fittingArgument is InternalResolvePlaceholder)
@ -537,7 +540,6 @@ public class IocContainer : IIocContainer, IIocResolver
exceptions.Add(new ConstructorNotMatchingException(constructor, new Exception("Not all given arguments were used!")));
return (false, parameters, exceptions);
}
/// <summary>

@ -356,6 +356,20 @@
<see cref="T:LightweightIocContainer.Interfaces.Factories.ITypedFactory"/> implementation for custom implemented factories
</summary>
</member>
<member name="T:LightweightIocContainer.Factories.FactoryHelper">
<summary>
Helper class for the <see cref="T:LightweightIocContainer.Factories.TypedFactory`1"/>
</summary>
</member>
<member name="M:LightweightIocContainer.Factories.FactoryHelper.ConvertPassedNull(System.Reflection.MethodBase,System.Object[])">
<summary>
Convert `null` passed as argument to <see cref="T:LightweightIocContainer.NullParameter"/> to handle it correctly
</summary>
<param name="createMethod">The create method of the factory</param>
<param name="arguments">The arguments passed to the create method</param>
<returns>List of arguments with converted null</returns>
<exception cref="T:System.Exception">Wrong parameters passed</exception>
</member>
<member name="T:LightweightIocContainer.Factories.TypedFactory`1">
<summary>
Class to help implement an abstract typed factory
@ -1197,6 +1211,22 @@
A new instance gets created if the given scope has no created instance yet. Otherwise the already created instance is used.
</summary>
</member>
<member name="T:LightweightIocContainer.NullParameter">
<summary>
Wrapper class to handle null passed as an argument correctly
</summary>
</member>
<member name="M:LightweightIocContainer.NullParameter.#ctor(System.Type)">
<summary>
Wrapper class to handle null passed as an argument correctly
</summary>
<param name="parameterType">The <see cref="T:System.Type"/> of the parameter</param>
</member>
<member name="P:LightweightIocContainer.NullParameter.ParameterType">
<summary>
The <see cref="T:System.Type"/> of the parameter
</summary>
</member>
<member name="T:LightweightIocContainer.Registrations.MultipleMultitonRegistration`3">
<summary>
An <see cref="T:LightweightIocContainer.Interfaces.Registrations.IRegistrationBase"/> to register multiple interfaces for on implementation type that implements them as a multiton

@ -0,0 +1,22 @@
// Author: Gockner, Simon
// Created: 2022-09-02
// Copyright(c) 2022 SimonG. All Rights Reserved.
namespace LightweightIocContainer;
/// <summary>
/// Wrapper class to handle null passed as an argument correctly
/// </summary>
public class NullParameter
{
/// <summary>
/// Wrapper class to handle null passed as an argument correctly
/// </summary>
/// <param name="parameterType">The <see cref="Type"/> of the parameter</param>
public NullParameter(Type parameterType) => ParameterType = parameterType;
/// <summary>
/// The <see cref="Type"/> of the parameter
/// </summary>
public Type ParameterType { get; }
}

@ -33,12 +33,21 @@ public class IocValidatorTest
public Test(IParameter parameter) => parameter.Method();
}
private class TestViewModel : ITest
[UsedImplicitly]
private class TestViewModelIgnoreDesignTimeCtor : ITest
{
public TestViewModel(IParameter parameter) => parameter.Method();
public TestViewModelIgnoreDesignTimeCtor(IParameter parameter) => parameter.Method();
[IocIgnoreConstructor]
public TestViewModel() => throw new Exception();
public TestViewModelIgnoreDesignTimeCtor() => throw new Exception();
}
[UsedImplicitly]
private class TestViewModelDontIgnoreDesignTimeCtor : ITest
{
public TestViewModelDontIgnoreDesignTimeCtor(string name, IParameter parameter) => parameter.Method();
public TestViewModelDontIgnoreDesignTimeCtor() => throw new Exception();
}
private class Parameter : IParameter
@ -52,6 +61,12 @@ public class IocValidatorTest
ITest Create(IParameter parameter);
}
[UsedImplicitly]
public interface ITestMissingParamFactory
{
ITest Create(string name);
}
[UsedImplicitly]
public interface IInvalidTestFactory
{
@ -97,11 +112,20 @@ public class IocValidatorTest
}
}
private class TestInstallerInvalidFactoryParameterDesignTimeCtorNotIgnoredRegisteredWithFactory : IIocInstaller
{
public void Install(IRegistrationCollector registration)
{
registration.Add<ITest, Test>().WithFactory<IInvalidTestFactory>();
registration.Add<IParameter, Parameter>().WithFactory<IParameterFactory>();
}
}
private class TestInstallerInvalidFactoryViewModel : IIocInstaller
{
public void Install(IRegistrationCollector registration)
{
registration.Add<ITest, TestViewModel>().WithFactory<IInvalidTestFactory>();
registration.Add<ITest, TestViewModelDontIgnoreDesignTimeCtor>().WithFactory<ITestMissingParamFactory>();
registration.Add<IParameter, Parameter>().WithFactory<IParameterFactory>();
}
}
@ -211,6 +235,31 @@ public class IocValidatorTest
Assert.IsInstanceOf<DirectResolveWithRegisteredFactoryNotAllowed>(iTest2CtorNotMatchingException.InnerExceptions[0]);
}
[Test]
public void TestValidateViewModelWithInvalidParameterDesignTimeCtorNotIgnoredWithFactory()
{
IocContainer iocContainer = new();
iocContainer.Install(new TestInstallerInvalidFactoryParameterDesignTimeCtorNotIgnoredRegisteredWithFactory());
IocValidator validator = new(iocContainer);
AggregateException aggregateException = Assert.Throws<AggregateException>(() => validator.Validate());
if (aggregateException?.InnerExceptions[0] is not NoMatchingConstructorFoundException noMatchingConstructorFoundException)
{
Assert.Fail($"First element of {nameof(aggregateException.InnerExceptions)} is not of type {nameof(NoMatchingConstructorFoundException)}.");
return;
}
if (noMatchingConstructorFoundException.InnerExceptions[0] is not ConstructorNotMatchingException iTest2CtorNotMatchingException)
{
Assert.Fail($"First element of {nameof(noMatchingConstructorFoundException.InnerExceptions)} is not of type {nameof(ConstructorNotMatchingException)}.");
return;
}
Assert.IsInstanceOf<DirectResolveWithRegisteredFactoryNotAllowed>(iTest2CtorNotMatchingException.InnerExceptions[0]);
}
[Test]
public void TestValidateInvalidFactory()

@ -43,7 +43,36 @@ public class FluentFactoryRegistrationTest
}
}
private class TestNull : ITest
{
public TestNull(object obj, string content, string optional1, string optional2, ITestNullFactory testNullFactory)
{
Obj = obj;
Content = content;
Optional1 = optional1;
Optional2 = optional2;
TestNullFactory = testNullFactory;
}
public object Obj { get; }
public string Content { get; }
public string Optional1 { get; }
public string Optional2 { get; }
public ITestNullFactory TestNullFactory { get; }
}
public interface ITestNullFactory
{
ITest Create(object obj, string content, string optional1, string optional2);
}
public interface ITestDefaultFactory
{
ITest Create(object obj, string content, string optional1, string optional2, ITestNullFactory testNullFactory = null);
}
private interface ITestFactoryNoCreate
{
@ -68,9 +97,9 @@ public class FluentFactoryRegistrationTest
private class TestFactory : ITestFactory
{
public ITest Create() => new Test();
public ITest Create(string name) => throw new System.NotImplementedException();
public ITest CreateTest(string name = null) => throw new System.NotImplementedException();
public ITest Create(byte id) => throw new System.NotImplementedException();
public ITest Create(string name) => throw new NotImplementedException();
public ITest CreateTest(string name = null) => throw new NotImplementedException();
public ITest Create(byte id) => throw new NotImplementedException();
}
[UsedImplicitly]
@ -210,6 +239,55 @@ public class FluentFactoryRegistrationTest
Assert.IsInstanceOf<TestByte>(createdTest);
}
[Test]
public void TestPassingNullAsMiddleParameter()
{
_iocContainer.Register(r => r.Add<ITest, TestNull>().WithFactory<ITestNullFactory>());
ITestNullFactory testNullFactory = _iocContainer.Resolve<ITestNullFactory>();
object obj = new();
string content = "TestContent";
string optional2 = "optionalParameter2";
ITest createdTest = testNullFactory.Create(obj, content, null, optional2);
if (createdTest is not TestNull testNull)
{
Assert.Fail();
return;
}
Assert.AreSame(obj, testNull.Obj);
Assert.AreEqual(content, testNull.Content);
Assert.AreEqual(null, testNull.Optional1);
Assert.AreEqual(optional2, testNull.Optional2);
}
[Test]
public void TestPassingNullAsDefaultParameter()
{
_iocContainer.Register(r => r.Add<ITest, TestNull>().WithFactory<ITestDefaultFactory>());
ITestDefaultFactory testDefaultFactory = _iocContainer.Resolve<ITestDefaultFactory>();
object obj = new();
string content = "TestContent";
string optional2 = "optionalParameter2";
ITest createdTest = testDefaultFactory.Create(obj, content, null, optional2);
if (createdTest is not TestNull testNull)
{
Assert.Fail();
return;
}
Assert.AreSame(obj, testNull.Obj);
Assert.AreEqual(content, testNull.Content);
Assert.AreEqual(null, testNull.Optional1);
Assert.AreEqual(optional2, testNull.Optional2);
Assert.AreEqual(null, testNull.TestNullFactory);
}
[Test]
public void TestResolveMultitonFromFactory()

Loading…
Cancel
Save