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

static member access for parameters of concrete types #291

Merged
merged 2 commits into from
Jan 8, 2022
Merged
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
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

3.3.1

* #287 - Fix static member access for concrete type parameters

3.3.0

* #276, #225, #167 - added support for constructors with arguments for complex types
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,12 @@ public object ConvertTo(Type toType, ResolutionContext resolutionContext)
if (convertor != null)
return convertor(argumentValue);

if ((toTypeInfo.IsInterface || toTypeInfo.IsAbstract) && !string.IsNullOrWhiteSpace(argumentValue))
if (!string.IsNullOrWhiteSpace(argumentValue))
{
//check if value looks like a static property or field directive
// check if value looks like a static property or field directive
// like "Namespace.TypeName::StaticProperty, AssemblyName"
if (TryParseStaticMemberAccessor(argumentValue, out var accessorTypeName, out var memberName))
if (toType != typeof(string) &&
TryParseStaticMemberAccessor(argumentValue, out var accessorTypeName, out var memberName))
{
var accessorType = Type.GetType(accessorTypeName, throwOnError: true);
// is there a public static property with that name ?
Expand Down Expand Up @@ -96,25 +97,28 @@ public object ConvertTo(Type toType, ResolutionContext resolutionContext)
throw new InvalidOperationException($"Could not find a public static property or field with name `{memberName}` on type `{accessorTypeName}`");
}

// maybe it's the assembly-qualified type name of a concrete implementation
// with a default constructor
var type = FindType(argumentValue.Trim());
if (type == null)
if (toTypeInfo.IsInterface || toTypeInfo.IsAbstract)
{
throw new InvalidOperationException($"Type {argumentValue} was not found.");
}
// maybe it's the assembly-qualified type name of a concrete implementation
// with a default constructor
var type = FindType(argumentValue.Trim());
if (type == null)
{
throw new InvalidOperationException($"Type {argumentValue} was not found.");
}

var ctor = type.GetTypeInfo().DeclaredConstructors.Where(ci => !ci.IsStatic).FirstOrDefault(ci =>
{
var parameters = ci.GetParameters();
return parameters.Length == 0 || parameters.All(pi => pi.HasDefaultValue);
});
var ctor = type.GetTypeInfo().DeclaredConstructors.Where(ci => !ci.IsStatic).FirstOrDefault(ci =>
{
var parameters = ci.GetParameters();
return parameters.Length == 0 || parameters.All(pi => pi.HasDefaultValue);
});

if (ctor == null)
throw new InvalidOperationException($"A default constructor was not found on {type.FullName}.");
if (ctor == null)
throw new InvalidOperationException($"A default constructor was not found on {type.FullName}.");

var call = ctor.GetParameters().Select(pi => pi.DefaultValue).ToArray();
return ctor.Invoke(call);
var call = ctor.GetParameters().Select(pi => pi.DefaultValue).ToArray();
return ctor.Invoke(call);
}
}

return Convert.ChangeType(argumentValue, toType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,16 +89,42 @@ public void FindTypeSupportsSimpleNamesForSerilogTypes(string input, Type target
[InlineData("Serilog.Settings.Configuration.Tests.Support.ClassWithStaticAccessors::AbstractProperty, Serilog.Settings.Configuration.Tests", typeof(AnAbstractClass))]
[InlineData("Serilog.Settings.Configuration.Tests.Support.ClassWithStaticAccessors::InterfaceField, Serilog.Settings.Configuration.Tests", typeof(IAmAnInterface))]
[InlineData("Serilog.Settings.Configuration.Tests.Support.ClassWithStaticAccessors::AbstractField, Serilog.Settings.Configuration.Tests", typeof(AnAbstractClass))]
public void StaticMembersAccessorsCanBeUsedForReferenceTypes(string input, Type targetType)
public void StaticMembersAccessorsCanBeUsedForAbstractTypes(string input, Type targetType)
{
var stringArgumentValue = new StringArgumentValue($"{input}");
var stringArgumentValue = new StringArgumentValue(input);

var actual = stringArgumentValue.ConvertTo(targetType, new ResolutionContext());

Assert.IsAssignableFrom(targetType, actual);
Assert.Equal(ConcreteImpl.Instance, actual);
}

[Theory]
[InlineData("Serilog.Settings.Configuration.Tests.Support.ClassWithStaticAccessors::ConcreteClassProperty, Serilog.Settings.Configuration.Tests", typeof(AConcreteClass))]
[InlineData("Serilog.Settings.Configuration.Tests.Support.ClassWithStaticAccessors::ConcreteClassField, Serilog.Settings.Configuration.Tests", typeof(AConcreteClass))]
public void StaticMembersAccessorsCanBeUsedForConcreteReferenceTypes(string input, Type targetType)
{
var stringArgumentValue = new StringArgumentValue(input);

var actual = stringArgumentValue.ConvertTo(targetType, new ResolutionContext());

Assert.IsAssignableFrom(targetType, actual);
Assert.Equal(ConcreteImplOfConcreteClass.Instance, actual);
}

[Theory]
[InlineData("Serilog.Settings.Configuration.Tests.Support.ClassWithStaticAccessors::IntProperty, Serilog.Settings.Configuration.Tests", typeof(int), 42)]
[InlineData("Serilog.Settings.Configuration.Tests.Support.ClassWithStaticAccessors::StringProperty, Serilog.Settings.Configuration.Tests", typeof(string),
"Serilog.Settings.Configuration.Tests.Support.ClassWithStaticAccessors::StringProperty, Serilog.Settings.Configuration.Tests")]
public void StaticMembersAccessorsCanBeUsedForBuiltInTypes(string input, Type targetType, object expected)
{
var stringArgumentValue = new StringArgumentValue(input);

var actual = stringArgumentValue.ConvertTo(targetType, new ResolutionContext());

Assert.Equal(expected, actual);
}

[Theory]
// unknown type
[InlineData("Namespace.ThisIsNotAKnownType::InterfaceProperty, Serilog.Settings.Configuration.Tests", typeof(IAmAnInterface))]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public abstract class AnAbstractClass
{
}

internal class ConcreteImpl : AnAbstractClass, IAmAnInterface
class ConcreteImpl : AnAbstractClass, IAmAnInterface
{
private ConcreteImpl()
{
Expand All @@ -17,13 +17,26 @@ private ConcreteImpl()
public static ConcreteImpl Instance { get; } = new ConcreteImpl();
}

public class AConcreteClass
{
}

class ConcreteImplOfConcreteClass : AConcreteClass
{
public static ConcreteImplOfConcreteClass Instance { get; } = new ConcreteImplOfConcreteClass();
}

public class ClassWithStaticAccessors
{
public static IAmAnInterface InterfaceProperty => ConcreteImpl.Instance;
public static AnAbstractClass AbstractProperty => ConcreteImpl.Instance;
public static AConcreteClass ConcreteClassProperty => ConcreteImplOfConcreteClass.Instance;
public static int IntProperty => 42;
public static string StringProperty => "don't see me";

public static IAmAnInterface InterfaceField = ConcreteImpl.Instance;
public static AnAbstractClass AbstractField = ConcreteImpl.Instance;
public static AConcreteClass ConcreteClassField = ConcreteImplOfConcreteClass.Instance;

// ReSharper disable once UnusedMember.Local
private static IAmAnInterface PrivateInterfaceProperty => ConcreteImpl.Instance;
Expand All @@ -34,4 +47,4 @@ public class ClassWithStaticAccessors
public IAmAnInterface InstanceInterfaceProperty => ConcreteImpl.Instance;
public IAmAnInterface InstanceInterfaceField = ConcreteImpl.Instance;
}
}
}