11using System . Diagnostics . CodeAnalysis ;
2- using System . Linq . Expressions ;
32using System . Reflection ;
43
54using Microsoft . Extensions . Configuration ;
@@ -46,13 +45,16 @@ public ObjectArgumentValue(IConfigurationSection section, IReadOnlyCollection<As
4645 if ( toType . IsArray )
4746 return CreateArray ( ) ;
4847
48+ // Only try to call ctor when type is explicitly specified in _section
49+ if ( TryCallCtorExplicit ( _section , resolutionContext , out var ctorResult ) )
50+ return ctorResult ;
51+
4952 if ( IsContainer ( toType , out var elementType ) && TryCreateContainer ( out var container ) )
5053 return container ;
5154
52- if ( TryBuildCtorExpression ( _section , toType , resolutionContext , out var ctorExpression ) )
53- {
54- return Expression . Lambda < Func < object > > ( ctorExpression ) . Compile ( ) . Invoke ( ) ;
55- }
55+ // Without a type explicitly specified, attempt to call ctor of toType
56+ if ( TryCallCtorImplicit ( _section , toType , resolutionContext , out ctorResult ) )
57+ return ctorResult ;
5658
5759 // MS Config binding can work with a limited set of primitive types and collections
5860 return _section . Get ( toType ) ;
@@ -76,33 +78,41 @@ bool TryCreateContainer([NotNullWhen(true)] out object? result)
7678 {
7779 result = null ;
7880
79- if ( toType . GetConstructor ( Type . EmptyTypes ) == null )
80- return false ;
81-
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 )
85- return false ;
81+ if ( IsConstructableDictionary ( toType , elementType , out var concreteType , out var keyType , out var valueType , out var addMethod ) )
82+ {
83+ result = Activator . CreateInstance ( concreteType ) ?? throw new InvalidOperationException ( $ "Activator.CreateInstance returned null for { concreteType } ") ;
8684
87- var configurationElements = _section . GetChildren ( ) . ToArray ( ) ;
88- result = Activator . CreateInstance ( toType ) ?? throw new InvalidOperationException ( $ "Activator.CreateInstance returned null for { toType } ") ;
85+ foreach ( var section in _section . GetChildren ( ) )
86+ {
87+ var argumentValue = ConfigurationReader . GetArgumentValue ( section , _configurationAssemblies ) ;
88+ var key = new StringArgumentValue ( section . Key ) . ConvertTo ( keyType , resolutionContext ) ;
89+ var value = argumentValue . ConvertTo ( valueType , resolutionContext ) ;
90+ addMethod . Invoke ( result , new [ ] { 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 } ") ;
8997
90- 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
91107 {
92- var argumentValue = ConfigurationReader . GetArgumentValue ( configurationElements [ i ] , _configurationAssemblies ) ;
93- var value = argumentValue . ConvertTo ( elementType , resolutionContext ) ;
94- addMethod . Invoke ( result , new [ ] { value } ) ;
108+ return false ;
95109 }
96-
97- return true ;
98110 }
99111 }
100112
101- internal static bool TryBuildCtorExpression (
102- IConfigurationSection section , Type parameterType , ResolutionContext resolutionContext , [ NotNullWhen ( true ) ] out NewExpression ? ctorExpression )
113+ bool TryCallCtorExplicit (
114+ IConfigurationSection section , ResolutionContext resolutionContext , [ NotNullWhen ( true ) ] out object ? value )
103115 {
104- ctorExpression = null ;
105-
106116 var typeDirective = section . GetValue < string > ( "$type" ) switch
107117 {
108118 not null => "$type" ,
@@ -116,21 +126,39 @@ internal static bool TryBuildCtorExpression(
116126 var type = typeDirective switch
117127 {
118128 not null => Type . GetType ( section . GetValue < string > ( typeDirective ) ! , throwOnError : false ) ,
119- null => parameterType ,
129+ null => null ,
120130 } ;
121131
122132 if ( type is null or { IsAbstract : true } )
123133 {
134+ value = null ;
124135 return false ;
125136 }
137+ else
138+ {
139+ var suppliedArguments = section . GetChildren ( ) . Where ( s => s . Key != typeDirective )
140+ . ToDictionary ( s => s . Key , StringComparer . OrdinalIgnoreCase ) ;
141+ return TryCallCtor ( type , suppliedArguments , resolutionContext , out value ) ;
142+ }
126143
127- var suppliedArguments = section . GetChildren ( ) . Where ( s => s . Key != typeDirective )
144+ }
145+
146+ bool TryCallCtorImplicit (
147+ IConfigurationSection section , Type parameterType , ResolutionContext resolutionContext , out object ? value )
148+ {
149+ var suppliedArguments = section . GetChildren ( )
128150 . ToDictionary ( s => s . Key , StringComparer . OrdinalIgnoreCase ) ;
151+ return TryCallCtor ( parameterType , suppliedArguments , resolutionContext , out value ) ;
152+ }
153+
154+ bool TryCallCtor ( Type type , Dictionary < string , IConfigurationSection > suppliedArguments , ResolutionContext resolutionContext , [ NotNullWhen ( true ) ] out object ? value )
155+ {
156+ value = null ;
129157
130158 if ( suppliedArguments . Count == 0 &&
131159 type . GetConstructor ( Type . EmptyTypes ) is ConstructorInfo parameterlessCtor )
132160 {
133- ctorExpression = Expression . New ( parameterlessCtor ) ;
161+ value = parameterlessCtor . Invoke ( [ ] ) ;
134162 return true ;
135163 }
136164
@@ -163,76 +191,126 @@ where gr.All(z => z.argumentBindResult.success)
163191 return false ;
164192 }
165193
166- var ctorArguments = new List < Expression > ( ) ;
167- foreach ( var argumentValue in ctor . ArgumentValues )
194+ var ctorArguments = new object ? [ ctor . ArgumentValues . Count ] ;
195+ for ( var i = 0 ; i < ctor . ArgumentValues . Count ; i ++ )
168196 {
169- if ( TryBindToCtorArgument ( argumentValue . Value , argumentValue . Type , resolutionContext , out var argumentExpression ) )
197+ var argument = ctor . ArgumentValues [ i ] ;
198+ var valueValue = argument . Value ;
199+ if ( valueValue is IConfigurationSection s )
170200 {
171- ctorArguments . Add ( argumentExpression ) ;
172- }
173- else
174- {
175- return false ;
201+ var argumentValue = ConfigurationReader . GetArgumentValue ( s , _configurationAssemblies ) ;
202+ valueValue = argumentValue . ConvertTo ( argument . Type , resolutionContext ) ;
176203 }
204+ ctorArguments [ i ] = valueValue ;
177205 }
178206
179- ctorExpression = Expression . New ( ctor . ConstructorInfo , ctorArguments ) ;
207+ value = ctor . ConstructorInfo . Invoke ( ctorArguments ) ;
180208 return true ;
209+ }
181210
182- static bool TryBindToCtorArgument ( object value , Type type , ResolutionContext resolutionContext , [ NotNullWhen ( true ) ] out Expression ? argumentExpression )
211+ static bool IsContainer ( Type type , [ NotNullWhen ( true ) ] out Type ? elementType )
212+ {
213+ elementType = null ;
214+ if ( type . IsGenericType && type . GetGenericTypeDefinition ( ) == typeof ( IEnumerable < > ) )
183215 {
184- argumentExpression = null ;
185-
186- if ( value is IConfigurationSection s )
216+ elementType = type . GetGenericArguments ( ) [ 0 ] ;
217+ return true ;
218+ }
219+ foreach ( var iface in type . GetInterfaces ( ) )
220+ {
221+ if ( iface . IsGenericType )
187222 {
188- if ( s . Value is string argValue )
223+ if ( iface . GetGenericTypeDefinition ( ) == typeof ( IEnumerable < > ) )
189224 {
190- var stringArgumentValue = new StringArgumentValue ( argValue ) ;
191- try
192- {
193- argumentExpression = Expression . Constant (
194- stringArgumentValue . ConvertTo ( type , resolutionContext ) ,
195- type ) ;
196-
197- return true ;
198- }
199- catch ( Exception )
200- {
201- return false ;
202- }
225+ elementType = iface . GetGenericArguments ( ) [ 0 ] ;
226+ return true ;
203227 }
204- else if ( s . GetChildren ( ) . Any ( ) )
205- {
206- if ( TryBuildCtorExpression ( s , type , resolutionContext , out var ctorExpression ) )
207- {
208- argumentExpression = ctorExpression ;
209- return true ;
210- }
228+ }
229+ }
211230
212- return false ;
231+ return false ;
232+ }
233+
234+ static bool IsConstructableDictionary ( Type type , Type elementType , [ NotNullWhen ( true ) ] out Type ? concreteType , [ NotNullWhen ( true ) ] out Type ? keyType , [ NotNullWhen ( true ) ] out Type ? valueType , [ NotNullWhen ( true ) ] out MethodInfo ? addMethod )
235+ {
236+ concreteType = null ;
237+ keyType = null ;
238+ valueType = null ;
239+ addMethod = null ;
240+ if ( ! elementType . IsGenericType || elementType . GetGenericTypeDefinition ( ) != typeof ( KeyValuePair < , > ) )
241+ {
242+ return false ;
243+ }
244+ var argumentTypes = elementType . GetGenericArguments ( ) ;
245+ keyType = argumentTypes [ 0 ] ;
246+ valueType = argumentTypes [ 1 ] ;
247+ if ( type . IsAbstract )
248+ {
249+ concreteType = typeof ( Dictionary < , > ) . MakeGenericType ( argumentTypes ) ;
250+ if ( ! type . IsAssignableFrom ( concreteType ) )
251+ {
252+ return false ;
253+ }
254+ }
255+ else
256+ {
257+ concreteType = type ;
258+ }
259+ if ( concreteType . GetConstructor ( Type . EmptyTypes ) == null )
260+ {
261+ return false ;
262+ }
263+ foreach ( var method in concreteType . GetMethods ( ) )
264+ {
265+ if ( ! method . IsStatic && method . Name == "Add" )
266+ {
267+ var parameters = method . GetParameters ( ) ;
268+ if ( parameters . Length == 2 && parameters [ 0 ] . ParameterType == keyType && parameters [ 1 ] . ParameterType == valueType )
269+ {
270+ addMethod = method ;
271+ return true ;
213272 }
214273 }
215-
216- argumentExpression = Expression . Constant ( value , type ) ;
217- return true ;
218274 }
275+ return false ;
219276 }
220277
221- static bool IsContainer ( Type type , [ NotNullWhen ( true ) ] out Type ? elementType )
278+ static bool IsConstructableContainer ( Type type , Type elementType , [ NotNullWhen ( true ) ] out Type ? concreteType , [ NotNullWhen ( true ) ] out MethodInfo ? addMethod )
222279 {
223- elementType = null ;
224- foreach ( var iface in type . GetInterfaces ( ) )
280+ addMethod = null ;
281+ if ( type . IsAbstract )
225282 {
226- if ( iface . IsGenericType )
283+ concreteType = typeof ( List < > ) . MakeGenericType ( elementType ) ;
284+ if ( ! type . IsAssignableFrom ( concreteType ) )
227285 {
228- if ( iface . GetGenericTypeDefinition ( ) == typeof ( IEnumerable < > ) )
286+ concreteType = typeof ( HashSet < > ) . MakeGenericType ( elementType ) ;
287+ if ( ! type . IsAssignableFrom ( concreteType ) )
229288 {
230- elementType = iface . GetGenericArguments ( ) [ 0 ] ;
289+ concreteType = null ;
290+ return false ;
291+ }
292+ }
293+ }
294+ else
295+ {
296+ concreteType = type ;
297+ }
298+ if ( concreteType . GetConstructor ( Type . EmptyTypes ) == null )
299+ {
300+ return false ;
301+ }
302+ foreach ( var method in concreteType . GetMethods ( ) )
303+ {
304+ if ( ! method . IsStatic && method . Name == "Add" )
305+ {
306+ var parameters = method . GetParameters ( ) ;
307+ if ( parameters . Length == 1 && parameters [ 0 ] . ParameterType == elementType )
308+ {
309+ addMethod = method ;
231310 return true ;
232311 }
233312 }
234313 }
235-
236314 return false ;
237315 }
238316}
0 commit comments