- 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. 35
      LightweightIocContainer/Factories/TypedFactory.cs
  4. 14
      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) 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;
}
}

@ -44,15 +44,21 @@ public class TypedFactory<TFactory> : TypedFactoryBase<TFactory>, ITypedFactory<
typeBuilder.AddInterfaceImplementation(factoryType); 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); 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 //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(); ILGenerator constructorGenerator = constructorBuilder.GetILGenerator();
constructorGenerator.Emit(OpCodes.Ldarg_0); constructorGenerator.Emit(OpCodes.Ldarg_0);
constructorGenerator.Emit(OpCodes.Ldarg_1); constructorGenerator.Emit(OpCodes.Ldarg_1);
constructorGenerator.Emit(OpCodes.Stfld, containerFieldBuilder); //set `_container` field 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); constructorGenerator.Emit(OpCodes.Ret);
foreach (MethodInfo createMethod in CreateMethods) foreach (MethodInfo createMethod in CreateMethods)
@ -60,7 +66,9 @@ public class TypedFactory<TFactory> : TypedFactoryBase<TFactory>, ITypedFactory<
//create a method that looks like this //create a method that looks like this
//public `createMethod.ReturnType` Create(`createMethod.GetParameters()`) //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(); ParameterInfo[] args = createMethod.GetParameters();
@ -70,9 +78,16 @@ public class TypedFactory<TFactory> : TypedFactoryBase<TFactory>, ITypedFactory<
typeBuilder.DefineMethodOverride(methodBuilder, createMethod); typeBuilder.DefineMethodOverride(methodBuilder, createMethod);
ILGenerator generator = methodBuilder.GetILGenerator(); 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.Ldarg_0);
generator.Emit(OpCodes.Ldfld, containerFieldBuilder); generator.Emit(OpCodes.Ldfld, helperFieldBuilder);
generator.Emit(OpCodes.Ldloc_0);
if (args.Any()) if (args.Any())
{ {
@ -94,7 +109,13 @@ public class TypedFactory<TFactory> : TypedFactoryBase<TFactory>, ITypedFactory<
generator.EmitCall(OpCodes.Call, emptyArray, null); generator.EmitCall(OpCodes.Call, emptyArray, null);
} }
generator.EmitCall(OpCodes.Call, typeof(IocContainer).GetMethod(nameof(IocContainer.FactoryResolve), new[] { typeof(object[]) })!.MakeGenericMethod(createMethod.ReturnType), 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.Emit(OpCodes.Castclass, createMethod.ReturnType); generator.Emit(OpCodes.Castclass, createMethod.ReturnType);
generator.Emit(OpCodes.Ret); generator.Emit(OpCodes.Ret);
} }
@ -106,7 +127,7 @@ public class TypedFactory<TFactory> : TypedFactoryBase<TFactory>, ITypedFactory<
//create a method that looks like this //create a method that looks like this
//public void ClearMultitonInstance<typeToClear>() //public void ClearMultitonInstance<typeToClear>()
//{ //{
// IIocContainer.ClearMultitonInstances<typeToClear>(); // IocContainer.ClearMultitonInstances<typeToClear>();
//} //}
if (multitonClearMethod.IsGenericMethod) 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> /// <typeparam name="T">The given <see cref="Type"/></typeparam>
/// <param name="arguments">The constructor arguments</param> /// <param name="arguments">The constructor arguments</param>
/// <returns>An instance of the given <see cref="Type"/></returns> /// <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> /// <summary>
/// Gets an instance of a given registered <see cref="Type"/> /// 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="resolveStack">The current resolve stack</param>
/// <param name="isFactoryResolve">True if resolve is called from factory, false (default) if not</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> /// <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> /// <summary>
/// Tries to resolve the given <see cref="Type"/> with the given arguments /// 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) private (bool result, List<object?>? parameters, List<ConstructorNotMatchingException>? exceptions) TryGetConstructorResolveStack(ConstructorInfo constructor, object?[]? arguments, List<Type> resolveStack)
{ {
List<ParameterInfo> constructorParameters = constructor.GetParameters().ToList(); List<ParameterInfo> constructorParameters = constructor.GetParameters().ToList();
if (!constructorParameters.Any())
return (true, null, null);
List<ConstructorNotMatchingException> exceptions = new(); List<ConstructorNotMatchingException> exceptions = new();
List<object?> parameters = new(); List<object?> parameters = new();
@ -500,10 +498,15 @@ public class IocContainer : IIocContainer, IIocResolver
if (passedArguments != null) if (passedArguments != null)
{ {
fittingArgument = passedArguments.FirstOrGiven<object?, InternalResolvePlaceholder>(a => 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) if (fittingArgument is not InternalResolvePlaceholder)
passedArguments.Remove(fittingArgument); passedArguments.Remove(fittingArgument);
if (fittingArgument is NullParameter)
fittingArgument = null;
} }
if (fittingArgument is InternalResolvePlaceholder) 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!"))); exceptions.Add(new ConstructorNotMatchingException(constructor, new Exception("Not all given arguments were used!")));
return (false, parameters, exceptions); return (false, parameters, exceptions);
} }
/// <summary> /// <summary>

@ -356,6 +356,20 @@
<see cref="T:LightweightIocContainer.Interfaces.Factories.ITypedFactory"/> implementation for custom implemented factories <see cref="T:LightweightIocContainer.Interfaces.Factories.ITypedFactory"/> implementation for custom implemented factories
</summary> </summary>
</member> </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"> <member name="T:LightweightIocContainer.Factories.TypedFactory`1">
<summary> <summary>
Class to help implement an abstract typed factory 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. A new instance gets created if the given scope has no created instance yet. Otherwise the already created instance is used.
</summary> </summary>
</member> </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"> <member name="T:LightweightIocContainer.Registrations.MultipleMultitonRegistration`3">
<summary> <summary>
An <see cref="T:LightweightIocContainer.Interfaces.Registrations.IRegistrationBase"/> to register multiple interfaces for on implementation type that implements them as a multiton 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(); 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] [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 private class Parameter : IParameter
@ -52,6 +61,12 @@ public class IocValidatorTest
ITest Create(IParameter parameter); ITest Create(IParameter parameter);
} }
[UsedImplicitly]
public interface ITestMissingParamFactory
{
ITest Create(string name);
}
[UsedImplicitly] [UsedImplicitly]
public interface IInvalidTestFactory 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 private class TestInstallerInvalidFactoryViewModel : IIocInstaller
{ {
public void Install(IRegistrationCollector registration) public void Install(IRegistrationCollector registration)
{ {
registration.Add<ITest, TestViewModel>().WithFactory<IInvalidTestFactory>(); registration.Add<ITest, TestViewModelDontIgnoreDesignTimeCtor>().WithFactory<ITestMissingParamFactory>();
registration.Add<IParameter, Parameter>().WithFactory<IParameterFactory>(); registration.Add<IParameter, Parameter>().WithFactory<IParameterFactory>();
} }
} }
@ -212,6 +236,31 @@ public class IocValidatorTest
Assert.IsInstanceOf<DirectResolveWithRegisteredFactoryNotAllowed>(iTest2CtorNotMatchingException.InnerExceptions[0]); 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] [Test]
public void TestValidateInvalidFactory() public void TestValidateInvalidFactory()
{ {

@ -44,6 +44,35 @@ 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 private interface ITestFactoryNoCreate
{ {
@ -68,9 +97,9 @@ public class FluentFactoryRegistrationTest
private class TestFactory : ITestFactory private class TestFactory : ITestFactory
{ {
public ITest Create() => new Test(); public ITest Create() => new Test();
public ITest Create(string name) => throw new System.NotImplementedException(); public ITest Create(string name) => throw new NotImplementedException();
public ITest CreateTest(string name = null) => throw new System.NotImplementedException(); public ITest CreateTest(string name = null) => throw new NotImplementedException();
public ITest Create(byte id) => throw new System.NotImplementedException(); public ITest Create(byte id) => throw new NotImplementedException();
} }
[UsedImplicitly] [UsedImplicitly]
@ -211,6 +240,55 @@ public class FluentFactoryRegistrationTest
Assert.IsInstanceOf<TestByte>(createdTest); 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] [Test]
public void TestResolveMultitonFromFactory() public void TestResolveMultitonFromFactory()
{ {

Loading…
Cancel
Save