@@ -46,13 +46,16 @@ public ObjectArgumentValue(IConfigurationSection section, IReadOnlyCollection<As
46
46
if ( toType . IsArray )
47
47
return CreateArray ( ) ;
48
48
49
+ // Only build ctor expression when type is explicitly specified in _section
50
+ if ( TryBuildCtorExpression ( _section , resolutionContext , out var ctorExpression ) )
51
+ return RunCtorExpression ( ctorExpression ) ;
52
+
49
53
if ( IsContainer ( toType , out var elementType ) && TryCreateContainer ( out var container ) )
50
54
return container ;
51
55
52
- if ( TryBuildCtorExpression ( _section , toType , resolutionContext , out var ctorExpression ) )
53
- {
54
- return Expression . Lambda < Func < object > > ( ctorExpression ) . Compile ( ) . Invoke ( ) ;
55
- }
56
+ // Without a type explicitly specified, attempt to create ctor expression of toType
57
+ if ( TryBuildCtorExpression ( _section , toType , resolutionContext , out ctorExpression ) )
58
+ return RunCtorExpression ( ctorExpression ) ;
56
59
57
60
// MS Config binding can work with a limited set of primitive types and collections
58
61
return _section . Get ( toType ) ;
@@ -76,31 +79,46 @@ bool TryCreateContainer([NotNullWhen(true)] out object? result)
76
79
{
77
80
result = null ;
78
81
79
- if ( toType . GetConstructor ( Type . EmptyTypes ) == null )
80
- return false ;
81
-
82
- if ( ! HasAddMethod ( toType , elementType , out var addMethod ) )
83
- return false ;
82
+ if ( IsConstructableDictionary ( toType , elementType , out var concreteType , out var addMethod ) )
83
+ {
84
+ result = Activator . CreateInstance ( concreteType ) ?? throw new InvalidOperationException ( $ "Activator.CreateInstance returned null for { concreteType } ") ;
84
85
85
- var configurationElements = _section . GetChildren ( ) . ToArray ( ) ;
86
- result = Activator . CreateInstance ( toType ) ?? throw new InvalidOperationException ( $ "Activator.CreateInstance returned null for { toType } ") ;
86
+ foreach ( var section in _section . GetChildren ( ) )
87
+ {
88
+ var argumentValue = ConfigurationReader . GetArgumentValue ( section , _configurationAssemblies ) ;
89
+ var value = argumentValue . ConvertTo ( elementType , resolutionContext ) ;
90
+ addMethod . Invoke ( result , new [ ] { section . Key , value } ) ;
91
+ }
92
+ return true ;
93
+ }
94
+ else if ( IsConstructableContainer ( toType , elementType , out concreteType , out addMethod ) )
95
+ {
96
+ result = Activator . CreateInstance ( concreteType ) ?? throw new InvalidOperationException ( $ "Activator.CreateInstance returned null for { concreteType } ") ;
87
97
88
- for ( int i = 0 ; i < configurationElements . Length ; ++ i )
98
+ foreach ( var section in _section . GetChildren ( ) )
99
+ {
100
+ var argumentValue = ConfigurationReader . GetArgumentValue ( section , _configurationAssemblies ) ;
101
+ var value = argumentValue . ConvertTo ( elementType , resolutionContext ) ;
102
+ addMethod . Invoke ( result , new [ ] { value } ) ;
103
+ }
104
+ return true ;
105
+ }
106
+ else
89
107
{
90
- var argumentValue = ConfigurationReader . GetArgumentValue ( configurationElements [ i ] , _configurationAssemblies ) ;
91
- var value = argumentValue . ConvertTo ( elementType , resolutionContext ) ;
92
- addMethod . Invoke ( result , new [ ] { value } ) ;
108
+ return false ;
93
109
}
94
-
95
- return true ;
96
110
}
97
111
}
98
112
99
- internal static bool TryBuildCtorExpression (
100
- IConfigurationSection section , Type parameterType , ResolutionContext resolutionContext , [ NotNullWhen ( true ) ] out NewExpression ? ctorExpression )
113
+ static object RunCtorExpression ( NewExpression ctorExpression )
101
114
{
102
- ctorExpression = null ;
115
+ Expression body = ctorExpression . Type . IsValueType ? Expression . Convert ( ctorExpression , typeof ( object ) ) : ctorExpression ;
116
+ return Expression . Lambda < Func < object > > ( body ) . Compile ( ) . Invoke ( ) ;
117
+ }
103
118
119
+ internal static bool TryBuildCtorExpression (
120
+ IConfigurationSection section , ResolutionContext resolutionContext , [ NotNullWhen ( true ) ] out NewExpression ? ctorExpression )
121
+ {
104
122
var typeDirective = section . GetValue < string > ( "$type" ) switch
105
123
{
106
124
not null => "$type" ,
@@ -114,16 +132,35 @@ internal static bool TryBuildCtorExpression(
114
132
var type = typeDirective switch
115
133
{
116
134
not null => Type . GetType ( section . GetValue < string > ( typeDirective ) ! , throwOnError : false ) ,
117
- null => parameterType ,
135
+ null => null ,
118
136
} ;
119
137
120
138
if ( type is null or { IsAbstract : true } )
121
139
{
140
+ ctorExpression = null ;
122
141
return false ;
123
142
}
143
+ else
144
+ {
145
+ var suppliedArguments = section . GetChildren ( ) . Where ( s => s . Key != typeDirective )
146
+ . ToDictionary ( s => s . Key , StringComparer . OrdinalIgnoreCase ) ;
147
+ return TryBuildCtorExpression ( type , suppliedArguments , resolutionContext , out ctorExpression ) ;
148
+ }
149
+
150
+ }
124
151
125
- var suppliedArguments = section . GetChildren ( ) . Where ( s => s . Key != typeDirective )
152
+ internal static bool TryBuildCtorExpression (
153
+ IConfigurationSection section , Type parameterType , ResolutionContext resolutionContext , [ NotNullWhen ( true ) ] out NewExpression ? ctorExpression )
154
+ {
155
+ var suppliedArguments = section . GetChildren ( )
126
156
. ToDictionary ( s => s . Key , StringComparer . OrdinalIgnoreCase ) ;
157
+ return TryBuildCtorExpression ( parameterType , suppliedArguments , resolutionContext , out ctorExpression ) ;
158
+ }
159
+
160
+ static bool TryBuildCtorExpression (
161
+ Type type , Dictionary < string , IConfigurationSection > suppliedArguments , ResolutionContext resolutionContext , [ NotNullWhen ( true ) ] out NewExpression ? ctorExpression )
162
+ {
163
+ ctorExpression = null ;
127
164
128
165
if ( suppliedArguments . Count == 0 &&
129
166
type . GetConstructor ( Type . EmptyTypes ) is ConstructorInfo parameterlessCtor )
@@ -219,27 +256,53 @@ static bool TryBindToCtorArgument(object value, Type type, ResolutionContext res
219
256
argumentExpression = Expression . NewArrayInit ( elementType , elements ) ;
220
257
return true ;
221
258
}
222
- else if ( IsContainer ( type , out elementType ) && type . GetConstructor ( Type . EmptyTypes ) is not null && HasAddMethod ( type , elementType , out var addMethod ) )
259
+ if ( TryBuildCtorExpression ( s , type , resolutionContext , out var ctorExpression ) )
223
260
{
224
- var elements = new List < Expression > ( ) ;
225
- foreach ( var element in s . GetChildren ( ) )
261
+ if ( ctorExpression . Type . IsValueType && ! type . IsValueType )
226
262
{
227
- if ( TryBindToCtorArgument ( element , elementType , resolutionContext , out var elementExpression ) )
263
+ argumentExpression = Expression . Convert ( ctorExpression , type ) ;
264
+ }
265
+ else {
266
+ argumentExpression = ctorExpression ;
267
+ }
268
+ return true ;
269
+ }
270
+ if ( IsContainer ( type , out elementType ) )
271
+ {
272
+ if ( IsConstructableDictionary ( type , elementType , out var concreteType , out var addMethod ) )
273
+ {
274
+ var elements = new List < ElementInit > ( ) ;
275
+ foreach ( var element in s . GetChildren ( ) )
228
276
{
229
- elements . Add ( elementExpression ) ;
277
+ if ( TryBindToCtorArgument ( element , elementType , resolutionContext , out var elementExpression ) )
278
+ {
279
+ elements . Add ( Expression . ElementInit ( addMethod , Expression . Constant ( element . Key ) , elementExpression ) ) ;
280
+ }
281
+ else
282
+ {
283
+ return false ;
284
+ }
230
285
}
231
- else
286
+ argumentExpression = Expression . ListInit ( Expression . New ( concreteType ) , elements ) ;
287
+ return true ;
288
+ }
289
+ if ( IsConstructableContainer ( type , elementType , out concreteType , out addMethod ) )
290
+ {
291
+ var elements = new List < Expression > ( ) ;
292
+ foreach ( var element in s . GetChildren ( ) )
232
293
{
233
- return false ;
294
+ if ( TryBindToCtorArgument ( element , elementType , resolutionContext , out var elementExpression ) )
295
+ {
296
+ elements . Add ( elementExpression ) ;
297
+ }
298
+ else
299
+ {
300
+ return false ;
301
+ }
234
302
}
303
+ argumentExpression = Expression . ListInit ( Expression . New ( concreteType ) , addMethod , elements ) ;
304
+ return true ;
235
305
}
236
- argumentExpression = Expression . ListInit ( Expression . New ( type ) , addMethod , elements ) ;
237
- return true ;
238
- }
239
- else if ( TryBuildCtorExpression ( s , type , resolutionContext , out var ctorExpression ) )
240
- {
241
- argumentExpression = ctorExpression ;
242
- return true ;
243
306
}
244
307
245
308
return false ;
@@ -254,6 +317,11 @@ static bool TryBindToCtorArgument(object value, Type type, ResolutionContext res
254
317
static bool IsContainer ( Type type , [ NotNullWhen ( true ) ] out Type ? elementType )
255
318
{
256
319
elementType = null ;
320
+ if ( type . IsGenericType && type . GetGenericTypeDefinition ( ) == typeof ( IEnumerable < > ) )
321
+ {
322
+ elementType = type . GetGenericArguments ( ) [ 0 ] ;
323
+ return true ;
324
+ }
257
325
foreach ( var iface in type . GetInterfaces ( ) )
258
326
{
259
327
if ( iface . IsGenericType )
@@ -269,10 +337,92 @@ static bool IsContainer(Type type, [NotNullWhen(true)] out Type? elementType)
269
337
return false ;
270
338
}
271
339
272
- static bool HasAddMethod ( Type type , Type elementType , [ NotNullWhen ( true ) ] out MethodInfo ? addMethod )
340
+ static bool IsConstructableDictionary ( Type type , Type elementType , [ NotNullWhen ( true ) ] out Type ? concreteType , [ NotNullWhen ( true ) ] out MethodInfo ? addMethod )
341
+ {
342
+ concreteType = null ;
343
+ addMethod = null ;
344
+ if ( ! elementType . IsGenericType || elementType . GetGenericTypeDefinition ( ) != typeof ( KeyValuePair < , > ) )
345
+ {
346
+ return false ;
347
+ }
348
+ var argumentTypes = elementType . GetGenericArguments ( ) ;
349
+ if ( argumentTypes [ 0 ] != typeof ( string ) )
350
+ {
351
+ return false ;
352
+ }
353
+ if ( ! typeof ( IDictionary < , > ) . MakeGenericType ( argumentTypes ) . IsAssignableFrom ( type )
354
+ && ! typeof ( IReadOnlyDictionary < , > ) . MakeGenericType ( argumentTypes ) . IsAssignableFrom ( type ) )
355
+ {
356
+ return false ;
357
+ }
358
+ if ( type . IsAbstract )
359
+ {
360
+ concreteType = typeof ( Dictionary < , > ) . MakeGenericType ( argumentTypes ) ;
361
+ if ( ! type . IsAssignableFrom ( concreteType ) )
362
+ {
363
+ return false ;
364
+ }
365
+ }
366
+ else
367
+ {
368
+ concreteType = type ;
369
+ }
370
+ if ( concreteType . GetConstructor ( Type . EmptyTypes ) == null )
371
+ {
372
+ return false ;
373
+ }
374
+ var valueType = argumentTypes [ 1 ] ;
375
+ foreach ( var method in concreteType . GetMethods ( ) )
376
+ {
377
+ if ( ! method . IsStatic && method . Name == "Add" )
378
+ {
379
+ var parameters = method . GetParameters ( ) ;
380
+ if ( parameters . Length == 2 && parameters [ 0 ] . ParameterType == typeof ( string ) && parameters [ 1 ] . ParameterType == valueType )
381
+ {
382
+ addMethod = method ;
383
+ return true ;
384
+ }
385
+ }
386
+ }
387
+ return false ;
388
+ }
389
+
390
+ static bool IsConstructableContainer ( Type type , Type elementType , [ NotNullWhen ( true ) ] out Type ? concreteType , [ NotNullWhen ( true ) ] out MethodInfo ? addMethod )
273
391
{
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 ;
392
+ addMethod = null ;
393
+ if ( type . IsAbstract )
394
+ {
395
+ concreteType = typeof ( List < > ) . MakeGenericType ( elementType ) ;
396
+ if ( ! type . IsAssignableFrom ( concreteType ) )
397
+ {
398
+ concreteType = typeof ( HashSet < > ) . MakeGenericType ( elementType ) ;
399
+ if ( ! type . IsAssignableFrom ( concreteType ) )
400
+ {
401
+ concreteType = null ;
402
+ return false ;
403
+ }
404
+ }
405
+ }
406
+ else
407
+ {
408
+ concreteType = type ;
409
+ }
410
+ if ( concreteType . GetConstructor ( Type . EmptyTypes ) == null )
411
+ {
412
+ return false ;
413
+ }
414
+ foreach ( var method in concreteType . GetMethods ( ) )
415
+ {
416
+ if ( ! method . IsStatic && method . Name == "Add" )
417
+ {
418
+ var parameters = method . GetParameters ( ) ;
419
+ if ( parameters . Length == 1 && parameters [ 0 ] . ParameterType == elementType )
420
+ {
421
+ addMethod = method ;
422
+ return true ;
423
+ }
424
+ }
425
+ }
426
+ return false ;
277
427
}
278
428
}
0 commit comments