Skip to content

Commit 736aa1a

Browse files
authored
Merge pull request #301 from almostchristian/support-static-delegate-types
Add support for delegate type values through StringArgumentValue #259
2 parents 375c5bb + 18053e9 commit 736aa1a

File tree

3 files changed

+68
-1
lines changed

3 files changed

+68
-1
lines changed

src/Serilog.Settings.Configuration/Settings/Configuration/StringArgumentValue.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,43 @@ public object ConvertTo(Type toType, ResolutionContext resolutionContext)
7171
TryParseStaticMemberAccessor(argumentValue, out var accessorTypeName, out var memberName))
7272
{
7373
var accessorType = Type.GetType(accessorTypeName, throwOnError: true);
74+
75+
// if delegate, look for a method and then construct a delegate
76+
if (typeof(Delegate).IsAssignableFrom(toType) || typeof(MethodInfo) == toType)
77+
{
78+
var methodCandidates = accessorType.GetTypeInfo().DeclaredMethods
79+
.Where(x => x.Name == memberName)
80+
.Where(x => x.IsPublic)
81+
.Where(x => !x.IsGenericMethod)
82+
.Where(x => x.IsStatic)
83+
.ToList();
84+
85+
if (methodCandidates.Count > 1)
86+
{
87+
// filter possible method overloads
88+
89+
var delegateSig = toType.GetMethod("Invoke");
90+
var delegateParameters = delegateSig!.GetParameters().Select(x => x.ParameterType);
91+
methodCandidates = methodCandidates
92+
.Where(x => x.ReturnType == delegateSig.ReturnType && x.GetParameters().Select(y => y.ParameterType).SequenceEqual(delegateParameters))
93+
.ToList();
94+
}
95+
96+
var methodCandidate = methodCandidates.SingleOrDefault();
97+
98+
if (methodCandidate != null)
99+
{
100+
if (typeof(MethodInfo) == toType)
101+
{
102+
return methodCandidate;
103+
}
104+
else
105+
{
106+
return methodCandidate.CreateDelegate(toType);
107+
}
108+
}
109+
}
110+
74111
// is there a public static property with that name ?
75112
var publicStaticPropertyInfo = accessorType.GetTypeInfo().DeclaredProperties
76113
.Where(x => x.Name == memberName)

test/Serilog.Settings.Configuration.Tests/StringArgumentValueTests.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,24 @@ public void StaticMembersAccessorsCanBeUsedForAbstractTypes(string input, Type t
9999
Assert.Equal(ConcreteImpl.Instance, actual);
100100
}
101101

102+
[Theory]
103+
[InlineData("Serilog.Settings.Configuration.Tests.Support.ClassWithStaticAccessors::FuncIntParseField, Serilog.Settings.Configuration.Tests", typeof(Func<string, int>))]
104+
[InlineData("Serilog.Settings.Configuration.Tests.Support.ClassWithStaticAccessors::NamedIntParseField, Serilog.Settings.Configuration.Tests", typeof(NamedIntParse))]
105+
[InlineData("Serilog.Settings.Configuration.Tests.Support.ClassWithStaticAccessors::FuncIntParseProperty, Serilog.Settings.Configuration.Tests", typeof(Func<string, int>))]
106+
[InlineData("Serilog.Settings.Configuration.Tests.Support.ClassWithStaticAccessors::NamedIntParseProperty, Serilog.Settings.Configuration.Tests", typeof(NamedIntParse))]
107+
[InlineData("Serilog.Settings.Configuration.Tests.Support.ClassWithStaticAccessors::IntParseMethod, Serilog.Settings.Configuration.Tests", typeof(NamedIntParse))]
108+
[InlineData("Serilog.Settings.Configuration.Tests.Support.ClassWithStaticAccessors::IntParseMethod, Serilog.Settings.Configuration.Tests", typeof(Func<string, int>))]
109+
public void StaticMembersAccessorsCanBeUsedForDelegateTypes(string input, Type targetType)
110+
{
111+
var stringArgumentValue = new StringArgumentValue(input);
112+
113+
var actual = stringArgumentValue.ConvertTo(targetType, new ResolutionContext());
114+
115+
Assert.IsAssignableFrom(targetType, actual);
116+
var parser = (Delegate)actual;
117+
Assert.Equal(100, parser.DynamicInvoke("100"));
118+
}
119+
102120
[Theory]
103121
[InlineData("Serilog.Settings.Configuration.Tests.Support.ClassWithStaticAccessors::ConcreteClassProperty, Serilog.Settings.Configuration.Tests", typeof(AConcreteClass))]
104122
[InlineData("Serilog.Settings.Configuration.Tests.Support.ClassWithStaticAccessors::ConcreteClassField, Serilog.Settings.Configuration.Tests", typeof(AConcreteClass))]

test/Serilog.Settings.Configuration.Tests/Support/StaticAccessorClasses.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
namespace Serilog.Settings.Configuration.Tests.Support
1+
using System;
2+
3+
namespace Serilog.Settings.Configuration.Tests.Support
24
{
5+
public delegate int NamedIntParse(string value);
6+
37
public interface IAmAnInterface
48
{
59
}
@@ -46,5 +50,13 @@ public class ClassWithStaticAccessors
4650
#pragma warning restore 169
4751
public IAmAnInterface InstanceInterfaceProperty => ConcreteImpl.Instance;
4852
public IAmAnInterface InstanceInterfaceField = ConcreteImpl.Instance;
53+
54+
public static Func<string, int> FuncIntParseField = int.Parse;
55+
public static NamedIntParse NamedIntParseField = int.Parse;
56+
public static Func<string, int> FuncIntParseProperty => int.Parse;
57+
public static NamedIntParse NamedIntParseProperty => int.Parse;
58+
public static int IntParseMethod(string value) => int.Parse(value);
59+
public static int IntParseMethod(string value, string otherValue) => throw new NotImplementedException(); // will not be chosen, extra parameter
60+
public static int IntParseMethod(object value) => throw new NotImplementedException(); // will not be chosen, wrong parameter type
4961
}
5062
}

0 commit comments

Comments
 (0)