@@ -38,25 +38,20 @@ public FactoryGenerator(Type delegateType, Service service, ParameterMapping par
3838
3939 Enforce . ArgumentTypeIsFunction ( delegateType ) ;
4040
41- _generator = CreateGenerator (
42- ( activatorContextParam , resolveParameterArray ) =>
43- {
44- // c, service, [new Parameter(name, (object)dps)]*
45- var resolveParams = new [ ]
46- {
47- activatorContextParam ,
48- Expression . Constant ( service , typeof ( Service ) ) ,
49- Expression . NewArrayInit ( typeof ( Parameter ) , resolveParameterArray ) ,
50- } ;
51-
52- // c.Resolve(...)
53- // default! here is for reflection only
54- return Expression . Call (
55- ReflectionExtensions . GetMethod < IComponentContext > ( cc => cc . ResolveService ( default ! , default ! ) ) ,
56- resolveParams ) ;
57- } ,
58- delegateType ,
59- GetParameterMapping ( delegateType , parameterMapping ) ) ;
41+ var pm = GetParameterMapping ( delegateType , parameterMapping ) ;
42+
43+ // Retrieve the cached compiled generator for this delegate type and parameter mapping,
44+ // or compile it once on first use. The cached delegate is parameterised on 'service'
45+ // (passed at invocation time) so it contains no instance-specific captures and is safe
46+ // to share across all FactoryGenerator instances with the same structural signature.
47+ // Accessing ReflectionCacheSet.Shared at point-of-use (not stored in a field) ensures
48+ // correct weak-reference collection of the cache set.
49+ var compiledGenerator = ReflectionCacheSet . Shared . Internal . GeneratedFactoryServiceOnlyGenerators . GetOrAdd (
50+ ( delegateType , pm ) ,
51+ static key => CreateServiceOnlyGenerator ( key . Item1 , key . Item2 ) ) ;
52+
53+ // Close over the specific service so _generator matches the expected signature.
54+ _generator = ( context , parameters ) => compiledGenerator ( service , context , parameters ) ;
6055 }
6156
6257 /// <summary>
@@ -72,27 +67,22 @@ public FactoryGenerator(Type delegateType, Service service, ServiceRegistration
7267 {
7368 Enforce . ArgumentTypeIsFunction ( delegateType ) ;
7469
75- _generator = CreateGenerator (
76- ( activatorContextParam , resolveParameterArray ) =>
77- {
78- // new ResolveRequest(service, productRegistration, [new Parameter(name, (object)dps)])*)
79- var newExpression = Expression . New (
80- _requestConstructor ,
81- Expression . Constant ( service , typeof ( Service ) ) ,
82- Expression . Constant ( productRegistration , typeof ( ServiceRegistration ) ) ,
83- Expression . NewArrayInit ( typeof ( Parameter ) , resolveParameterArray ) ,
84- Expression . Constant ( null , typeof ( IComponentRegistration ) ) ) ;
70+ var pm = GetParameterMapping ( delegateType , parameterMapping ) ;
8571
86- // c.Resolve(...)
87- // default! for reflection only
88- return Expression . Call (
89- activatorContextParam ,
90- ReflectionExtensions . GetMethod < IComponentContext > ( cc => cc . ResolveComponent (
91- new ResolveRequest ( default ! , default , default ( Parameter [ ] ) ! , default ) ) ) ,
92- newExpression ) ;
93- } ,
94- delegateType ,
95- GetParameterMapping ( delegateType , parameterMapping ) ) ;
72+ // Retrieve the cached compiled generator for this delegate type and parameter mapping,
73+ // or compile it once on first use. The cached delegate is parameterised on both 'service'
74+ // and 'productRegistration' (passed at invocation time), so it contains no instance-specific
75+ // captures and is safe to share across all FactoryGenerator instances for the same delegate type.
76+ // Accessing ReflectionCacheSet.Shared at point-of-use (not stored in a field) ensures
77+ // correct weak-reference collection of the cache set.
78+ var compiledGenerator = ReflectionCacheSet . Shared . Internal . GeneratedFactoryServiceRegistrationGenerators . GetOrAdd (
79+ ( delegateType , pm ) ,
80+ static key => CreateServiceRegistrationGenerator ( key . Item1 , key . Item2 ) ) ;
81+
82+ // Close over the specific service and product registration so _generator matches the
83+ // expected signature. These values are instance-specific but are not baked into the
84+ // compiled expression tree — they are passed as arguments at each invocation.
85+ _generator = ( context , parameters ) => compiledGenerator ( service , productRegistration , context , parameters ) ;
9686 }
9787
9888 /// <summary>
@@ -144,13 +134,18 @@ private static bool DelegateTypeIsFunc(Type delegateType)
144134 return delegateType . Name . StartsWith ( "Func`" , StringComparison . Ordinal ) ;
145135 }
146136
147- private static Func < IComponentContext , IEnumerable < Parameter > , Delegate > CreateGenerator ( Func < Expression , Expression [ ] , Expression > makeResolveCall , Type delegateType , ParameterMapping pm )
137+ /// <summary>
138+ /// Creates a compiled generator for the overload that resolves via <c>ResolveService</c>.
139+ /// The returned delegate accepts <paramref name="delegateType"/>-specific parameters at runtime and contains
140+ /// no instance-specific compile-time captures, making it safe to cache and share across all
141+ /// <see cref="FactoryGenerator"/> instances with the same (<paramref name="delegateType"/>, <paramref name="pm"/>) pair.
142+ /// </summary>
143+ private static Func < Service , IComponentContext , IEnumerable < Parameter > , Delegate > CreateServiceOnlyGenerator ( Type delegateType , ParameterMapping pm )
148144 {
149- // (c, p) => ([dps]*) => (drt)Resolve(c, productRegistration, [new NamedParameter(name, (object)dps)]* )
150- // (c, p)
145+ // Outer parameters: (Service svc, IComponentContext c, IEnumerable<Parameter> p )
146+ var serviceParam = Expression . Parameter ( typeof ( Service ) , "svc" ) ;
151147 var activatorContextParam = Expression . Parameter ( typeof ( IComponentContext ) , "c" ) ;
152148 var activatorParamsParam = Expression . Parameter ( typeof ( IEnumerable < Parameter > ) , "p" ) ;
153- var activatorParams = new [ ] { activatorContextParam , activatorParamsParam } ;
154149
155150 var invoke = delegateType . GetDeclaredMethod ( "Invoke" ) ;
156151
@@ -163,18 +158,81 @@ private static Func<IComponentContext, IEnumerable<Parameter>, Delegate> CreateG
163158 Expression ? resolveCast = null ;
164159 if ( DelegateTypeIsFunc ( delegateType ) && pm == ParameterMapping . ByType )
165160 {
166- // Issue #269:
167- // If we're resolving a Func<X1...XN>() and there are duplicate input parameter types
168- // and the parameter mapping is by type, we shouldn't be able to resolve it.
161+ // Issue #269: duplicate input parameter types are not allowed for ByType mapping.
169162 var arguments = delegateType . GenericTypeArguments ;
170163 var returnType = arguments [ arguments . Length - 1 ] ;
164+ Array . Resize ( ref arguments , arguments . Length - 1 ) ;
165+ if ( arguments . Distinct ( ) . Count ( ) != arguments . Length )
166+ {
167+ object [ ] argumentsArray = arguments . ToArray ( ) ;
168+ var message = string . Format ( CultureInfo . CurrentCulture , GeneratedFactoryRegistrationSourceResources . DuplicateTypesInTypeMappedFuncParameterList , returnType . AssemblyQualifiedName , string . Join ( ", " , argumentsArray ) ) ;
169+ resolveCast = Expression . Throw ( Expression . Constant ( new DependencyResolutionException ( message ) ) , invoke . ReturnType ) ;
170+ }
171+ }
172+
173+ if ( resolveCast == null )
174+ {
175+ var resolveParameterArray = MapParameters ( creatorParams , pm ) ;
171176
172- // Remove the return type to check the list of input types only.
177+ // c.ResolveService(svc, [new Parameter(...)]*) — 'svc' is supplied as a runtime parameter.
178+ var resolveParams = new Expression [ ]
179+ {
180+ activatorContextParam ,
181+ serviceParam ,
182+ Expression . NewArrayInit ( typeof ( Parameter ) , resolveParameterArray ) ,
183+ } ;
184+
185+ var resolveCall = Expression . Call (
186+ ReflectionExtensions . GetMethod < IComponentContext > ( cc => cc . ResolveService ( default ! , default ! ) ) ,
187+ resolveParams ) ;
188+
189+ resolveCast = Expression . Convert ( resolveCall , invoke . ReturnType ) ;
190+ }
191+
192+ // ([dps]*) => (drt)c.ResolveService(svc, [...])
193+ var creator = Expression . Lambda ( delegateType , resolveCast , creatorParams ) ;
194+
195+ // (svc, c, p) => ([dps]*) => ...
196+ var activator = Expression . Lambda < Func < Service , IComponentContext , IEnumerable < Parameter > , Delegate > > (
197+ creator ,
198+ serviceParam ,
199+ activatorContextParam ,
200+ activatorParamsParam ) ;
201+
202+ return activator . Compile ( ) ;
203+ }
204+
205+ /// <summary>
206+ /// Creates a compiled generator for the overload that resolves via <see cref="IComponentContext.ResolveComponent"/>.
207+ /// The returned delegate accepts <paramref name="delegateType"/>-specific parameters at runtime and contains
208+ /// no instance-specific compile-time captures, making it safe to cache and share across all
209+ /// <see cref="FactoryGenerator"/> instances with the same (<paramref name="delegateType"/>, <paramref name="pm"/>) pair.
210+ /// </summary>
211+ private static Func < Service , ServiceRegistration , IComponentContext , IEnumerable < Parameter > , Delegate > CreateServiceRegistrationGenerator ( Type delegateType , ParameterMapping pm )
212+ {
213+ // Outer parameters: (Service svc, ServiceRegistration sr, IComponentContext c, IEnumerable<Parameter> p)
214+ var serviceParam = Expression . Parameter ( typeof ( Service ) , "svc" ) ;
215+ var serviceRegistrationParam = Expression . Parameter ( typeof ( ServiceRegistration ) , "sr" ) ;
216+ var activatorContextParam = Expression . Parameter ( typeof ( IComponentContext ) , "c" ) ;
217+ var activatorParamsParam = Expression . Parameter ( typeof ( IEnumerable < Parameter > ) , "p" ) ;
218+
219+ var invoke = delegateType . GetDeclaredMethod ( "Invoke" ) ;
220+
221+ // [dps]*
222+ var creatorParams = invoke
223+ . GetParameters ( )
224+ . Select ( pi => Expression . Parameter ( pi . ParameterType , pi . Name ) )
225+ . ToList ( ) ;
226+
227+ Expression ? resolveCast = null ;
228+ if ( DelegateTypeIsFunc ( delegateType ) && pm == ParameterMapping . ByType )
229+ {
230+ // Issue #269: duplicate input parameter types are not allowed for ByType mapping.
231+ var arguments = delegateType . GenericTypeArguments ;
232+ var returnType = arguments [ arguments . Length - 1 ] ;
173233 Array . Resize ( ref arguments , arguments . Length - 1 ) ;
174234 if ( arguments . Distinct ( ) . Count ( ) != arguments . Length )
175235 {
176- // There are duplicate input types - that's a problem. Throw
177- // when the function is invoked.
178236 object [ ] argumentsArray = arguments . ToArray ( ) ;
179237 var message = string . Format ( CultureInfo . CurrentCulture , GeneratedFactoryRegistrationSourceResources . DuplicateTypesInTypeMappedFuncParameterList , returnType . AssemblyQualifiedName , string . Join ( ", " , argumentsArray ) ) ;
180238 resolveCast = Expression . Throw ( Expression . Constant ( new DependencyResolutionException ( message ) ) , invoke . ReturnType ) ;
@@ -183,23 +241,36 @@ private static Func<IComponentContext, IEnumerable<Parameter>, Delegate> CreateG
183241
184242 if ( resolveCast == null )
185243 {
186- // Issue #269: There aren't duplicate parameter types in the generated
187- // factory, so in the case of Func<X1...XN>() typed parameter mapping,
188- // if there are duplicate types in the target constructor, both constructor
189- // parameters will get the same value passed in. (We don't know
190- // the activator, so we can't do much more about it than that.)
191- //
192- // (drt)
193244 var resolveParameterArray = MapParameters ( creatorParams , pm ) ;
194- var resolveCall = makeResolveCall ( activatorContextParam , resolveParameterArray ) ;
245+
246+ // new ResolveRequest(svc, sr, [...], null) — 'svc' and 'sr' are runtime parameters.
247+ var newExpression = Expression . New (
248+ _requestConstructor ,
249+ serviceParam ,
250+ serviceRegistrationParam ,
251+ Expression . NewArrayInit ( typeof ( Parameter ) , resolveParameterArray ) ,
252+ Expression . Constant ( null , typeof ( IComponentRegistration ) ) ) ;
253+
254+ // c.ResolveComponent(new ResolveRequest(...))
255+ var resolveCall = Expression . Call (
256+ activatorContextParam ,
257+ ReflectionExtensions . GetMethod < IComponentContext > ( cc => cc . ResolveComponent (
258+ new ResolveRequest ( default ! , default , default ( Parameter [ ] ) ! , default ) ) ) ,
259+ newExpression ) ;
260+
195261 resolveCast = Expression . Convert ( resolveCall , invoke . ReturnType ) ;
196262 }
197263
198- // ([dps]*) => c.Resolve(service, [ new Parameter(name, dps)]* )
264+ // ([dps]*) => (drt)c.ResolveComponent( new ResolveRequest(svc, sr, [...]) )
199265 var creator = Expression . Lambda ( delegateType , resolveCast , creatorParams ) ;
200266
201- // (c, p) => (
202- var activator = Expression . Lambda < Func < IComponentContext , IEnumerable < Parameter > , Delegate > > ( creator , activatorParams ) ;
267+ // (svc, sr, c, p) => ([dps]*) => ...
268+ var activator = Expression . Lambda < Func < Service , ServiceRegistration , IComponentContext , IEnumerable < Parameter > , Delegate > > (
269+ creator ,
270+ serviceParam ,
271+ serviceRegistrationParam ,
272+ activatorContextParam ,
273+ activatorParamsParam ) ;
203274
204275 return activator . Compile ( ) ;
205276 }
0 commit comments