diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/StringArgumentValue.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/StringArgumentValue.cs index 69dceb3..6d94639 100644 --- a/src/Serilog.Settings.Configuration/Settings/Configuration/StringArgumentValue.cs +++ b/src/Serilog.Settings.Configuration/Settings/Configuration/StringArgumentValue.cs @@ -71,6 +71,43 @@ public object ConvertTo(Type toType, ResolutionContext resolutionContext) TryParseStaticMemberAccessor(argumentValue, out var accessorTypeName, out var memberName)) { var accessorType = Type.GetType(accessorTypeName, throwOnError: true); + + // if delegate, look for a method and then construct a delegate + if (typeof(Delegate).IsAssignableFrom(toType) || typeof(MethodInfo) == toType) + { + var methodCandidates = accessorType.GetTypeInfo().DeclaredMethods + .Where(x => x.Name == memberName) + .Where(x => x.IsPublic) + .Where(x => !x.IsGenericMethod) + .Where(x => x.IsStatic) + .ToList(); + + if (methodCandidates.Count > 1) + { + // filter possible method overloads + + var delegateSig = toType.GetMethod("Invoke"); + var delegateParameters = delegateSig!.GetParameters().Select(x => x.ParameterType); + methodCandidates = methodCandidates + .Where(x => x.ReturnType == delegateSig.ReturnType && x.GetParameters().Select(y => y.ParameterType).SequenceEqual(delegateParameters)) + .ToList(); + } + + var methodCandidate = methodCandidates.SingleOrDefault(); + + if (methodCandidate != null) + { + if (typeof(MethodInfo) == toType) + { + return methodCandidate; + } + else + { + return methodCandidate.CreateDelegate(toType); + } + } + } + // is there a public static property with that name ? var publicStaticPropertyInfo = accessorType.GetTypeInfo().DeclaredProperties .Where(x => x.Name == memberName) diff --git a/test/Serilog.Settings.Configuration.Tests/StringArgumentValueTests.cs b/test/Serilog.Settings.Configuration.Tests/StringArgumentValueTests.cs index 8b455d5..642e85e 100644 --- a/test/Serilog.Settings.Configuration.Tests/StringArgumentValueTests.cs +++ b/test/Serilog.Settings.Configuration.Tests/StringArgumentValueTests.cs @@ -99,6 +99,24 @@ public void StaticMembersAccessorsCanBeUsedForAbstractTypes(string input, Type t Assert.Equal(ConcreteImpl.Instance, actual); } + [Theory] + [InlineData("Serilog.Settings.Configuration.Tests.Support.ClassWithStaticAccessors::FuncIntParseField, Serilog.Settings.Configuration.Tests", typeof(Func))] + [InlineData("Serilog.Settings.Configuration.Tests.Support.ClassWithStaticAccessors::NamedIntParseField, Serilog.Settings.Configuration.Tests", typeof(NamedIntParse))] + [InlineData("Serilog.Settings.Configuration.Tests.Support.ClassWithStaticAccessors::FuncIntParseProperty, Serilog.Settings.Configuration.Tests", typeof(Func))] + [InlineData("Serilog.Settings.Configuration.Tests.Support.ClassWithStaticAccessors::NamedIntParseProperty, Serilog.Settings.Configuration.Tests", typeof(NamedIntParse))] + [InlineData("Serilog.Settings.Configuration.Tests.Support.ClassWithStaticAccessors::IntParseMethod, Serilog.Settings.Configuration.Tests", typeof(NamedIntParse))] + [InlineData("Serilog.Settings.Configuration.Tests.Support.ClassWithStaticAccessors::IntParseMethod, Serilog.Settings.Configuration.Tests", typeof(Func))] + public void StaticMembersAccessorsCanBeUsedForDelegateTypes(string input, Type targetType) + { + var stringArgumentValue = new StringArgumentValue(input); + + var actual = stringArgumentValue.ConvertTo(targetType, new ResolutionContext()); + + Assert.IsAssignableFrom(targetType, actual); + var parser = (Delegate)actual; + Assert.Equal(100, parser.DynamicInvoke("100")); + } + [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))] diff --git a/test/Serilog.Settings.Configuration.Tests/Support/StaticAccessorClasses.cs b/test/Serilog.Settings.Configuration.Tests/Support/StaticAccessorClasses.cs index 03b8357..7db6f99 100644 --- a/test/Serilog.Settings.Configuration.Tests/Support/StaticAccessorClasses.cs +++ b/test/Serilog.Settings.Configuration.Tests/Support/StaticAccessorClasses.cs @@ -1,5 +1,9 @@ -namespace Serilog.Settings.Configuration.Tests.Support +using System; + +namespace Serilog.Settings.Configuration.Tests.Support { + public delegate int NamedIntParse(string value); + public interface IAmAnInterface { } @@ -46,5 +50,13 @@ public class ClassWithStaticAccessors #pragma warning restore 169 public IAmAnInterface InstanceInterfaceProperty => ConcreteImpl.Instance; public IAmAnInterface InstanceInterfaceField = ConcreteImpl.Instance; + + public static Func FuncIntParseField = int.Parse; + public static NamedIntParse NamedIntParseField = int.Parse; + public static Func FuncIntParseProperty => int.Parse; + public static NamedIntParse NamedIntParseProperty => int.Parse; + public static int IntParseMethod(string value) => int.Parse(value); + public static int IntParseMethod(string value, string otherValue) => throw new NotImplementedException(); // will not be chosen, extra parameter + public static int IntParseMethod(object value) => throw new NotImplementedException(); // will not be chosen, wrong parameter type } }