Skip to content

Commit 0ad2ebe

Browse files
Support collections as constructor arguments
1 parent 1d0e7bf commit 0ad2ebe

File tree

3 files changed

+118
-4
lines changed

3 files changed

+118
-4
lines changed

src/Serilog.Settings.Configuration/Settings/Configuration/ObjectArgumentValue.cs

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,7 @@ bool TryCreateContainer([NotNullWhen(true)] out object? result)
7979
if (toType.GetConstructor(Type.EmptyTypes) == null)
8080
return false;
8181

82-
// https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/object-and-collection-initializers#collection-initializers
83-
var addMethod = toType.GetMethods().FirstOrDefault(m => !m.IsStatic && m.Name == "Add" && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == elementType);
84-
if (addMethod == null)
82+
if (!HasAddMethod(toType, elementType, out var addMethod))
8583
return false;
8684

8785
var configurationElements = _section.GetChildren().ToArray();
@@ -203,7 +201,42 @@ static bool TryBindToCtorArgument(object value, Type type, ResolutionContext res
203201
}
204202
else if (s.GetChildren().Any())
205203
{
206-
if (TryBuildCtorExpression(s, type, resolutionContext, out var ctorExpression))
204+
var elementType = type.GetElementType();
205+
if (elementType is not null)
206+
{
207+
var elements = new List<Expression>();
208+
foreach (var element in s.GetChildren())
209+
{
210+
if (TryBindToCtorArgument(element, elementType, resolutionContext, out var elementExpression))
211+
{
212+
elements.Add(elementExpression);
213+
}
214+
else
215+
{
216+
return false;
217+
}
218+
}
219+
argumentExpression = Expression.NewArrayInit(elementType, elements);
220+
return true;
221+
}
222+
else if (IsContainer(type, out elementType) && type.GetConstructor(Type.EmptyTypes) is not null && HasAddMethod(type, elementType, out var addMethod))
223+
{
224+
var elements = new List<Expression>();
225+
foreach (var element in s.GetChildren())
226+
{
227+
if (TryBindToCtorArgument(element, elementType, resolutionContext, out var elementExpression))
228+
{
229+
elements.Add(elementExpression);
230+
}
231+
else
232+
{
233+
return false;
234+
}
235+
}
236+
argumentExpression = Expression.ListInit(Expression.New(type), addMethod, elements);
237+
return true;
238+
}
239+
else if (TryBuildCtorExpression(s, type, resolutionContext, out var ctorExpression))
207240
{
208241
argumentExpression = ctorExpression;
209242
return true;
@@ -235,4 +268,11 @@ static bool IsContainer(Type type, [NotNullWhen(true)] out Type? elementType)
235268

236269
return false;
237270
}
271+
272+
static bool HasAddMethod(Type type, Type elementType, [NotNullWhen(true)] out MethodInfo? addMethod)
273+
{
274+
// https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/object-and-collection-initializers#collection-initializers
275+
addMethod = type.GetMethods().FirstOrDefault(m => !m.IsStatic && m.Name == "Add" && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == elementType);
276+
return addMethod is not null;
277+
}
238278
}

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

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using Microsoft.Extensions.Configuration;
2+
using System.Collections;
3+
using System.Linq.Expressions;
24

35
// ReSharper disable UnusedMember.Local
46
// ReSharper disable UnusedParameter.Local
@@ -24,6 +26,9 @@ public ObjectArgumentValueTests()
2426
[InlineData("case_4", typeof(F), "new F(\"paramType\", new E(1, 2, 3, 4))")]
2527
[InlineData("case_5", typeof(G), "new G()")]
2628
[InlineData("case_6", typeof(G), "new G(3, 4)")]
29+
[InlineData("case_7", typeof(H), "new H(new [] {\"1\", \"2\"})")]
30+
[InlineData("case_8", typeof(H), "new H(new [] {new D(), new I()})")]
31+
[InlineData("case_9", typeof(H), "new H(new J`1() {Void Add(C)(new D()), Void Add(C)(new I())})")]
2732
public void ShouldBindToConstructorArguments(string caseSection, Type targetType, string expectedExpression)
2833
{
2934
var testSection = _config.GetSection(caseSection);
@@ -32,6 +37,19 @@ public void ShouldBindToConstructorArguments(string caseSection, Type targetType
3237
Assert.Equal(expectedExpression, ctorExpression.ToString());
3338
}
3439

40+
[Fact]
41+
public void ShouldBindToConstructorContainerArguments()
42+
{
43+
var testSection = _config.GetSection("case_9");
44+
45+
Assert.True(ObjectArgumentValue.TryBuildCtorExpression(testSection, typeof(H), new(), out var ctorExpression));
46+
var instance = Expression.Lambda<Func<H>>(ctorExpression).Compile()();
47+
Assert.IsType<J<C>>(instance.Collection);
48+
Assert.Collection(instance.Collection,
49+
first => Assert.IsType<D>(first),
50+
second => Assert.IsType<I>(second));
51+
}
52+
3553
class A
3654
{
3755
public A(int a, TimeSpan b, Uri c, string d = "d") { }
@@ -64,4 +82,34 @@ class G
6482
public G() { }
6583
public G(int a = 1, int b = 2) { }
6684
}
85+
86+
class H
87+
{
88+
public J<C>? Collection { get; }
89+
90+
public H(params string[] strings) { }
91+
public H(C[] array) { }
92+
public H(J<C> collection) { Collection = collection; }
93+
}
94+
95+
class I : C { }
96+
97+
class J<T> : IEnumerable<T>
98+
{
99+
private List<T> list = new();
100+
public void Add(T value)
101+
{
102+
list.Add(value);
103+
}
104+
105+
public IEnumerator<T> GetEnumerator()
106+
{
107+
return list.GetEnumerator();
108+
}
109+
110+
IEnumerator IEnumerable.GetEnumerator()
111+
{
112+
return list.GetEnumerator();
113+
}
114+
}
67115
}

test/Serilog.Settings.Configuration.Tests/ObjectArgumentValueTests.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,31 @@
4040
"case_6": {
4141
"a": 3,
4242
"b": 4
43+
},
44+
45+
"case_7": {
46+
"strings": [ "1", "2" ]
47+
},
48+
49+
"case_8": {
50+
"array": [
51+
{
52+
"type": "Serilog.Settings.Configuration.Tests.ObjectArgumentValueTests+D, Serilog.Settings.Configuration.Tests"
53+
},
54+
{
55+
"type": "Serilog.Settings.Configuration.Tests.ObjectArgumentValueTests+I, Serilog.Settings.Configuration.Tests"
56+
}
57+
]
58+
},
59+
60+
"case_9": {
61+
"collection": [
62+
{
63+
"type": "Serilog.Settings.Configuration.Tests.ObjectArgumentValueTests+D, Serilog.Settings.Configuration.Tests"
64+
},
65+
{
66+
"type": "Serilog.Settings.Configuration.Tests.ObjectArgumentValueTests+I, Serilog.Settings.Configuration.Tests"
67+
}
68+
]
4369
}
4470
}

0 commit comments

Comments
 (0)