Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Types can be resolved where the constructor has a default parameter, … #105

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 99 additions & 1 deletion src/TinyIoC.Tests/TestData/BasicClasses.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace TinyIoC.Tests.TestData
{
Expand Down Expand Up @@ -193,6 +193,104 @@ public GenericClassWithInterface(I prop1, S prop2)
}
}

internal class TestClassWithOptionalNullParameter
{
public TestClassNoInterfaceDefaultCtor OptionalReferenceTypeParameter { get; private set; }

public TestClassWithOptionalNullParameter(TestClassNoInterfaceDefaultCtor optionalReferenceTypeParameter = null)
{
OptionalReferenceTypeParameter = optionalReferenceTypeParameter;
}
}

internal class TestClassWithDependentOptionalNullParameter
{
public TestClassWithOptionalStringParameter OptionalReferenceTypeParameter { get; private set; }

public TestClassWithDependentOptionalNullParameter(TestClassWithOptionalStringParameter optionalReferenceTypeParameter = null)
{
OptionalReferenceTypeParameter = optionalReferenceTypeParameter;
}
}

internal class TestClassWithOptionalValueTypeParameter
{
public DateTime OptionalValueTypeParameter { get; private set; }

public TestClassWithOptionalValueTypeParameter(DateTime optionalValueTypeParameter = default(DateTime))
{
OptionalValueTypeParameter = optionalValueTypeParameter;
}
}

internal class TestClassWithOptionalIntParameter
{
public const int DefaultIntParameterValue = 10;
public int OptionalIntParameter { get; private set; }

public TestClassWithOptionalIntParameter(int optionalIntParameter = DefaultIntParameterValue)
{
OptionalIntParameter = optionalIntParameter;
}
}

internal class TestClassWithOptionalIntParameterViaAttribute
{
public const int DefaultIntParameterValue = 100;
public int OptionalIntParameter { get; private set; }

public TestClassWithOptionalIntParameterViaAttribute([Optional, DefaultParameterValue(DefaultIntParameterValue)]int optionalIntParameter)
{
OptionalIntParameter = optionalIntParameter;
}
}

internal class TestClassWithOptionalDefaultIntParameterViaAttribute
{
public const int DefaultIntParameterValue = default(int);
public int OptionalIntParameter { get; private set; }

public TestClassWithOptionalDefaultIntParameterViaAttribute([Optional, DefaultParameterValue(default(int))]int optionalIntParameter)
{
OptionalIntParameter = optionalIntParameter;
}
}

internal class TestClassWithOptionalStringParameter
{
public const string DefaultStringParameterValue = "Default string";

public string OptionalStringParameter { get; private set; }

public TestClassWithOptionalStringParameter(string optionalStringParameter = DefaultStringParameterValue)
{
OptionalStringParameter = optionalStringParameter;
}
}

internal class TestClassWithOptionalStringParameterViaAttribute
{
public const string DefaultStringParameterValue = "Default string via attribute";

public string OptionalStringParameter { get; private set; }

public TestClassWithOptionalStringParameterViaAttribute([Optional, DefaultParameterValue(DefaultStringParameterValue)]string optionalStringParameter)
{
OptionalStringParameter = optionalStringParameter;
}
}

internal class TestClassWithOptionalNullStringParameterViaAttribute
{
public const string DefaultStringParameterValue = default(string);

public string OptionalStringParameter { get; private set; }

public TestClassWithOptionalNullStringParameterViaAttribute([Optional, DefaultParameterValue(default(string))]string optionalStringParameter)
{
OptionalStringParameter = optionalStringParameter;
}
}

internal class GenericClassWithParametersAndDependencies<I, S>
{
Expand Down
184 changes: 184 additions & 0 deletions src/TinyIoC.Tests/TinyIoCTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1625,6 +1625,135 @@ public void Resolve_ConstructorSpecifiedThatRequiresParametersButNonePassed_Fail
//Assert.IsTrue(false);
}

[TestMethod]
public void Resolve_ConstructorWithOptionalNullParameter_Resolves()
{
var container = UtilityMethods.GetContainer();
container.Register<TestClassWithOptionalNullParameter>();

var result = container.Resolve<TestClassWithOptionalNullParameter>();

Assert.IsNotNull(result);
}

[TestMethod]
public void Resolve_ConstructorWithDependentOptionalNullParameter_Resolves()
{
var container = UtilityMethods.GetContainer();
container.Register<TestClassWithDependentOptionalNullParameter>();

var result = container.Resolve<TestClassWithDependentOptionalNullParameter>();
Assert.IsTrue(result.OptionalReferenceTypeParameter.OptionalStringParameter == TestClassWithOptionalStringParameter.DefaultStringParameterValue);
}

[TestMethod]
public void Resolve_ConstructorWithOptionalValueTypeParameter_Resolves()
{
var container = UtilityMethods.GetContainer();
container.Register<TestClassWithOptionalValueTypeParameter>();

var result = container.Resolve<TestClassWithOptionalValueTypeParameter>();

Assert.IsTrue(result.OptionalValueTypeParameter == default(DateTime));
}

[TestMethod]
public void Resolve_ConstructorWithOptionaInt10Parameter_Resolves()
{
var container = UtilityMethods.GetContainer();
container.Register<TestClassWithOptionalIntParameter>();

var result = container.Resolve<TestClassWithOptionalIntParameter>();

Assert.IsTrue(result.OptionalIntParameter == 10);
}

[TestMethod]
public void Resolve_ConstructorWithOptionalStringParameter_Resolves()
{
var container = UtilityMethods.GetContainer();
container.Register<TestClassWithOptionalStringParameter>();

var result = container.Resolve<TestClassWithOptionalStringParameter>();

Assert.IsTrue(result.OptionalStringParameter == "Default string");
}

[TestMethod]
public void Resolve_ConstructorWithOptionaIntParameterViaAttribute_Resolves()
{
var container = UtilityMethods.GetContainer();
container.Register<TestClassWithOptionalIntParameterViaAttribute>();

var result = container.Resolve<TestClassWithOptionalIntParameterViaAttribute>();

Assert.IsTrue(result.OptionalIntParameter == TestClassWithOptionalIntParameterViaAttribute.DefaultIntParameterValue);
}

[TestMethod]
public void Resolve_ConstructorWithOptionaDefaultIntParameterViaAttribute_Resolves()
{
var container = UtilityMethods.GetContainer();
container.Register<TestClassWithOptionalDefaultIntParameterViaAttribute>();

var result = container.Resolve<TestClassWithOptionalDefaultIntParameterViaAttribute>();

Assert.IsTrue(result.OptionalIntParameter == TestClassWithOptionalDefaultIntParameterViaAttribute.DefaultIntParameterValue);
}

/// <summary>
/// Check that a default value is used only if a parameter value is not explicitly set.
/// </summary>
[TestMethod]
public void Resolve_ConstructorWithOptionaNonDefaultIntParameter_Resolves()
{
const int value = 200;
var container = UtilityMethods.GetContainer();
container.Register(new TestClassWithOptionalIntParameter(value));

var result = container.Resolve<TestClassWithOptionalIntParameter>();

Assert.IsTrue(result.OptionalIntParameter == value);
}

/// <summary>
/// Check that a default value is used only if a parameter value is not explicitly set.
/// </summary>
[TestMethod]
public void Resolve_ConstructorWithOptionaNonDefaultStringParameter_Resolves()
{
const string value = "Non default string value";
var container = UtilityMethods.GetContainer();
container.Register(new TestClassWithOptionalStringParameter(value));

var result = container.Resolve<TestClassWithOptionalStringParameter>();

Assert.IsTrue(result.OptionalStringParameter == value);
}

[TestMethod]
public void Resolve_ConstructorWithOptionalStringParameterViaAttribute_Resolves()
{
var container = UtilityMethods.GetContainer();
container.Register<TestClassWithOptionalStringParameterViaAttribute>();

var result = container.Resolve<TestClassWithOptionalStringParameterViaAttribute>();

Assert.IsTrue(result.OptionalStringParameter == TestClassWithOptionalStringParameterViaAttribute.DefaultStringParameterValue);
}

[TestMethod]
public void Resolve_ConstructorWithOptionalNullStringParameterViaAttribute_Resolves()
{
var container = UtilityMethods.GetContainer();
container.Register<TestClassWithOptionalNullStringParameterViaAttribute>();

var result = container.Resolve<TestClassWithOptionalNullStringParameterViaAttribute>();

Assert.IsTrue(result.OptionalStringParameter == TestClassWithOptionalNullStringParameterViaAttribute.DefaultStringParameterValue);
}


[TestMethod]
public void CanResolve_ConstructorSpecifiedThatRequiresParametersButNonePassed_ReturnsFalse()
{
Expand All @@ -1637,6 +1766,61 @@ public void CanResolve_ConstructorSpecifiedThatRequiresParametersButNonePassed_R
Assert.IsFalse(result);
}

[TestMethod]
public void CanResolve_ConstructorWithOptionalNullParameter_ReturnsTrue()
{
var container = UtilityMethods.GetContainer();
container.Register<TestClassWithOptionalNullParameter>();

var result = container.CanResolve<TestClassWithOptionalNullParameter>();

Assert.IsTrue(result);
}

[TestMethod]
public void CanResolve_ConstructorWithDependentOptionalNullParameter_ReturnsTrue()
{
var container = UtilityMethods.GetContainer();
container.Register<TestClassWithDependentOptionalNullParameter>();

var result = container.CanResolve<TestClassWithDependentOptionalNullParameter>();

Assert.IsTrue(result);
}

[TestMethod]
public void CanResolve_ConstructorWithOptionalValueTypeParameter_ReturnsTrue()
{
var container = UtilityMethods.GetContainer();
container.Register<TestClassWithOptionalValueTypeParameter>();

var result = container.CanResolve<TestClassWithOptionalValueTypeParameter>();

Assert.IsTrue(result);
}

[TestMethod]
public void CanResolve_ConstructorWithOptionaInt10Parameter_ReturnsTrue()
{
var container = UtilityMethods.GetContainer();
container.Register<TestClassWithOptionalIntParameter>();

var result = container.CanResolve<TestClassWithOptionalIntParameter>();

Assert.IsTrue(result);
}

[TestMethod]
public void CanResolve_ConstructorWithOptionalStringParameter_ReturnsTrue()
{
var container = UtilityMethods.GetContainer();
container.Register<TestClassWithOptionalStringParameter>();

var result = container.CanResolve<TestClassWithOptionalStringParameter>();

Assert.IsTrue(result);
}

[TestMethod]
public void CanResolve_SingletonFactoryConstructorSpecified_ReturnsTrue()
{
Expand Down
44 changes: 38 additions & 6 deletions src/TinyIoC/TinyIoC.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3926,6 +3926,9 @@ private bool CanConstruct(ConstructorInfo ctor, NamedParameterOverloads paramete

foreach (var parameter in ctor.GetParameters())
{
if (parameter.IsOptional)
return true;

if (string.IsNullOrEmpty(parameter.Name))
return false;

Expand Down Expand Up @@ -3994,6 +3997,8 @@ private object ConstructType(Type requestedType, Type implementationType, NamedP
return ConstructType(requestedType, implementationType, null, parameters, options);
}

private static readonly SafeDictionary<Type, object> _DefaultValues = new SafeDictionary<Type, object>();

private object ConstructType(Type requestedType, Type implementationType, ConstructorInfo constructor, NamedParameterOverloads parameters, ResolveOptions options)
{
var typeToConstruct = implementationType;
Expand Down Expand Up @@ -4028,12 +4033,39 @@ private object ConstructType(Type requestedType, Type implementationType, Constr

try
{
args[parameterIndex] = parameters.ContainsKey(currentParam.Name) ?
parameters[currentParam.Name] :
ResolveInternal(
new TypeRegistration(currentParam.ParameterType),
NamedParameterOverloads.Default,
options);
object parameterValue;
if (parameters.TryGetValue(currentParam.Name, out parameterValue))
{
args[parameterIndex] = parameterValue;
}
else
{
if (currentParam.IsOptional && (currentParam.ParameterType == typeof(string) || currentParam.ParameterType.IsValueType()))
{
try
{
args[parameterIndex] = currentParam.DefaultValue;
}
catch
{
// The currentParam.DefaultValue is not always valid, e.g. with default(DateTime), we get a 'SystemFormatException'- possibly due to culture differences.
// If so, Activator.CreateInstance(Type) *should* always work.
object defaultValue;
if (!_DefaultValues.TryGetValue(currentParam.ParameterType, out defaultValue))
{
// Potentially creating 'defaultValue' twice in multi-threaded writes isn't a problem; all instances arae equivalent and it'll only happen once,
// so even if Activator.CreateInstance(Type) were unpleasantly slow, it wouldn't really matter.
defaultValue = Activator.CreateInstance(currentParam.ParameterType);
_DefaultValues[currentParam.ParameterType] = defaultValue;
}
args[parameterIndex] = defaultValue;
}
}
else
{
args[parameterIndex] = ResolveInternal(new TypeRegistration(currentParam.ParameterType), NamedParameterOverloads.Default, options);
}
}
}
catch (TinyIoCResolutionException ex)
{
Expand Down