@@ -131,25 +131,15 @@ private static Expression<Func<TInput, TOutput>> ArrayConversionExpression<TInpu
131131 indexer = i => ArrayAccess ( input , i ) ;
132132 break ;
133133
134- // Input is typed as an IList - we can get its length and index into it
134+ // Input is typed as an ICollection - we can get its length, but we can't index into it
135135 case { IsGenericType : true } when inputInterfaces . Append ( input . Type )
136- . Any ( i => i . IsGenericType && i . GetGenericTypeDefinition ( ) == typeof ( IList < > ) ) :
136+ . Any ( i => i . IsGenericType && i . GetGenericTypeDefinition ( ) == typeof ( ICollection < > ) ) :
137137 {
138138 getInputLength = Property (
139139 input ,
140140 input . Type . GetProperty ( "Count" )
141- // If TInput is an interface (IList <T>), its Count property needs to be found on ICollection<T>
141+ // If TInput is an interface (ICollection <T>), its Count property needs to be found on ICollection<T>
142142 ?? typeof ( ICollection < > ) . MakeGenericType ( input . Type . GetGenericArguments ( ) [ 0 ] ) . GetProperty ( "Count" ) ! ) ;
143- indexer = i => Property ( input , input . Type . FindIndexerProperty ( ) ! , i ) ;
144- break ;
145- }
146-
147- // Input is typed as an ICollection - we can get its length, but we can't index into it
148- case { IsGenericType : true } when inputInterfaces . Append ( input . Type )
149- . Any ( i => i . IsGenericType && i . GetGenericTypeDefinition ( ) == typeof ( ICollection < > ) ) :
150- {
151- getInputLength = Property (
152- input , typeof ( ICollection < > ) . MakeGenericType ( input . Type . GetGenericArguments ( ) [ 0 ] ) . GetProperty ( "Count" ) ! ) ;
153143 indexer = null ;
154144 break ;
155145 }
@@ -176,6 +166,31 @@ private static Expression<Func<TInput, TOutput>> ArrayConversionExpression<TInpu
176166 throw new NotSupportedException ( $ "Array value converter input type must be an IEnumerable, but is { typeof ( TInput ) } ") ;
177167 }
178168
169+ Expression ? instantiateOutput = typeof ( TConcreteOutput ) switch
170+ {
171+ var t when t . IsArray => NewArrayBounds ( outputElementType , lengthVariable ) ,
172+ var t when typeof ( TConcreteOutput ) . GetConstructor ( [ typeof ( int ) ] ) is ConstructorInfo ctorWithLength => New ( ctorWithLength , lengthVariable ) ,
173+ var t when typeof ( TConcreteOutput ) . GetConstructor ( [ ] ) is not null => New ( typeof ( TConcreteOutput ) ) ,
174+
175+ _ => null
176+ } ;
177+
178+ if ( instantiateOutput is null )
179+ {
180+ // If the output type can't be instantiated (no public parameterless constructor), we can't value convert to it.
181+ // If we simply throw and prevent the array converter from being instantiated, that would block scenarios where the user is
182+ // only *writing* an uninstantiable type, but never reading it (see #3050). To allow for such "one-directional" value converters,
183+ // we instead return a lambda that throws, so that if converter is actually ever used to read, it will throw at that point.
184+ // See test Parameter_collection_Dictionary_Valuees_with_value_converter_Contains.
185+ return Lambda < Func < TInput , TOutput > > (
186+ Throw (
187+ New (
188+ typeof ( InvalidOperationException ) . GetConstructor ( [ typeof ( string ) ] ) ! ,
189+ Constant ( $ "Type { typeof ( TConcreteOutput ) } cannot be instantiated as it does not have a public parameterless constructor.") ) ,
190+ typeof ( TOutput ) ) ,
191+ input ) ;
192+ }
193+
179194 expressions . AddRange (
180195 [
181196 // Get the length of the input array or list
@@ -184,14 +199,7 @@ private static Expression<Func<TInput, TOutput>> ArrayConversionExpression<TInpu
184199
185200 // Allocate an output array or list
186201 // var result = new int[length];
187- Assign (
188- output ,
189- typeof ( TConcreteOutput ) switch
190- {
191- var t when t . IsArray => NewArrayBounds ( outputElementType , lengthVariable ) ,
192- var t when typeof ( TConcreteOutput ) . GetConstructor ( [ typeof ( int ) ] ) is ConstructorInfo ctorWithLength => New ( ctorWithLength , lengthVariable ) ,
193- _ => New ( typeof ( TConcreteOutput ) )
194- } )
202+ Assign ( output , instantiateOutput )
195203 ] ) ;
196204
197205 if ( indexer is not null )
@@ -200,7 +208,7 @@ var t when typeof(TConcreteOutput).GetConstructor([typeof(int)]) is ConstructorI
200208 // the element converter on each element.
201209 // for (var i = 0; i < length; i++)
202210 // {
203- // result[i] = input[i];
211+ // result[i] = convert( input[i]) ;
204212 // }
205213 var counter = Parameter ( typeof ( int ) , "i" ) ;
206214
@@ -232,7 +240,7 @@ elementConversionExpression is null
232240 // counter = 0;
233241 // while (enumerator.MoveNext())
234242 // {
235- // output[counter] = enumerator.Current;
243+ // output[counter] = convert( enumerator.Current) ;
236244 // counter++;
237245 // }
238246 var enumerableType = typeof ( IEnumerable < > ) . MakeGenericType ( inputElementType ) ;
0 commit comments