@@ -43,7 +43,7 @@ sealed class Candidate
4343 public readonly bool IsExpandedForm ;
4444
4545 /// <summary>
46- /// Gets the parameter types. In the first step, these are the types without any substition .
46+ /// Gets the parameter types. In the first step, these are the types without any substitution .
4747 /// After type inference, substitutions will be performed.
4848 /// </summary>
4949 public readonly IType [ ] ParameterTypes ;
@@ -76,14 +76,32 @@ sealed class Candidate
7676 /// </summary>
7777 public Conversion [ ] ArgumentConversions ;
7878
79+ /// <summary>
80+ /// Gets the type of the collection that is used for the 'params' parameter (before any substitution!).
81+ /// Otherwise returns SpecialType.UnknownType.
82+ /// </summary>
83+ public IType ParamsCollectionType {
84+ get {
85+ if ( IsExpandedForm && Parameters . Count > 0 )
86+ {
87+ IParameter lastParameter = Parameters [ Parameters . Count - 1 ] ;
88+ if ( lastParameter . IsParams )
89+ {
90+ return lastParameter . Type ;
91+ }
92+ }
93+ return SpecialType . UnknownType ;
94+ }
95+ }
96+
7997 public bool IsGenericMethod {
8098 get {
8199 IMethod method = Member as IMethod ;
82100 return method != null && method . TypeParameters . Count > 0 ;
83101 }
84102 }
85103
86- public int ArgumentsPassedToParamsArray {
104+ public int ArgumentsPassedToParams {
87105 get {
88106 int count = 0 ;
89107 if ( IsExpandedForm )
@@ -104,7 +122,7 @@ public Candidate(IParameterizedMember member, bool isExpanded)
104122 this . Member = member ;
105123 this . IsExpandedForm = isExpanded ;
106124 IParameterizedMember memberDefinition = ( IParameterizedMember ) member . MemberDefinition ;
107- // For specificialized methods, go back to the original parameters:
125+ // For specialized methods, go back to the original parameters:
108126 // (without any type parameter substitution, not even class type parameters)
109127 // We'll re-substitute them as part of RunTypeInference().
110128 this . Parameters = memberDefinition . Parameters ;
@@ -286,9 +304,12 @@ bool ResolveParameterTypes(Candidate candidate, bool useSpecializedParameters)
286304 }
287305 if ( candidate . IsExpandedForm && i == candidate . Parameters . Count - 1 )
288306 {
289- ArrayType arrayType = type as ArrayType ;
290- if ( arrayType != null && arrayType . Dimensions == 1 )
307+ if ( type is ArrayType arrayType && arrayType . Dimensions == 1 )
291308 type = arrayType . ElementType ;
309+ else if ( type . IsKnownType ( KnownTypeCode . ReadOnlySpanOfT ) || type . IsKnownType ( KnownTypeCode . SpanOfT ) )
310+ type = type . TypeArguments [ 0 ] ;
311+ else if ( type . IsArrayInterfaceType ( ) )
312+ type = type . TypeArguments [ 0 ] ;
292313 else
293314 return false ; // error: cannot unpack params-array. abort considering the expanded form for this candidate
294315 }
@@ -772,8 +793,8 @@ int BetterFunctionMember(Candidate c1, Candidate c2)
772793 else if ( c1 . IsExpandedForm && ! c2 . IsExpandedForm )
773794 return 2 ;
774795
775- // prefer the member with less arguments mapped to the params-array
776- int r = c1 . ArgumentsPassedToParamsArray . CompareTo ( c2 . ArgumentsPassedToParamsArray ) ;
796+ // prefer the member with less arguments mapped to the params-collection
797+ int r = c1 . ArgumentsPassedToParams . CompareTo ( c2 . ArgumentsPassedToParams ) ;
777798 if ( r < 0 )
778799 return 1 ;
779800 else if ( r > 0 )
@@ -797,13 +818,105 @@ int BetterFunctionMember(Candidate c1, Candidate c2)
797818 return 1 ;
798819 if ( lift1 != null && lift2 == null )
799820 return 2 ;
821+
822+ // prefer by-value parameters over in-parameters
823+ r = BetterParameterPassingChoice ( c1 , c2 ) ;
824+ if ( r != 0 )
825+ return r ;
826+
827+ if ( c1 . IsExpandedForm )
828+ {
829+ Debug . Assert ( c2 . IsExpandedForm ) ;
830+ r = BetterParamsCollectionType ( c1 . ParamsCollectionType , c2 . ParamsCollectionType ) ;
831+ if ( r != 0 )
832+ return r ;
833+ }
834+ }
835+ return 0 ;
836+ }
837+
838+ int BetterParamsCollectionType ( IType paramsCollectionType1 , IType paramsCollectionType2 )
839+ {
840+ // see https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-13.0/params-collections#better-function-member
841+ bool isSpan1 = paramsCollectionType1 . IsKnownType ( KnownTypeCode . SpanOfT ) || paramsCollectionType1 . IsKnownType ( KnownTypeCode . ReadOnlySpanOfT ) ;
842+ bool isSpan2 = paramsCollectionType2 . IsKnownType ( KnownTypeCode . SpanOfT ) || paramsCollectionType2 . IsKnownType ( KnownTypeCode . ReadOnlySpanOfT ) ;
843+ if ( ! isSpan1 && ! isSpan2 )
844+ {
845+ bool implicitConversion1to2 = conversions . ImplicitConversion ( paramsCollectionType1 , paramsCollectionType2 ) . IsValid ;
846+ bool implicitConversion2to1 = conversions . ImplicitConversion ( paramsCollectionType2 , paramsCollectionType1 ) . IsValid ;
847+ if ( implicitConversion1to2 && ! implicitConversion2to1 )
848+ return 1 ;
849+ if ( ! implicitConversion1to2 && implicitConversion2to1 )
850+ return 2 ;
851+ }
852+ else if ( paramsCollectionType1 . IsKnownType ( KnownTypeCode . ReadOnlySpanOfT ) && paramsCollectionType2 . IsKnownType ( KnownTypeCode . SpanOfT ) )
853+ {
854+ if ( conversions . IdentityConversion ( paramsCollectionType1 . TypeArguments [ 0 ] , paramsCollectionType2 . TypeArguments [ 0 ] ) )
855+ return 1 ; // ReadOnlySpan<T> is better than Span<T> if there exists an identity conversion between the element types
800856 }
857+ else if ( paramsCollectionType2 . IsKnownType ( KnownTypeCode . ReadOnlySpanOfT ) && paramsCollectionType1 . IsKnownType ( KnownTypeCode . SpanOfT ) )
858+ {
859+ if ( conversions . IdentityConversion ( paramsCollectionType2 . TypeArguments [ 0 ] , paramsCollectionType1 . TypeArguments [ 0 ] ) )
860+ return 2 ; // ReadOnlySpan<T> is better than Span<T> if there exists an identity conversion between the element types
861+ }
862+ else if ( isSpan1 && IsArrayOrArrayInterfaceType ( paramsCollectionType2 , out var elementType2 ) )
863+ {
864+ if ( conversions . IdentityConversion ( paramsCollectionType1 . TypeArguments [ 0 ] , elementType2 ) )
865+ return 1 ; // Span<T> is better than an array type if there exists an identity conversion between the element types
866+ }
867+ else if ( isSpan2 && IsArrayOrArrayInterfaceType ( paramsCollectionType1 , out var elementType1 ) )
868+ {
869+ if ( conversions . IdentityConversion ( paramsCollectionType2 . TypeArguments [ 0 ] , elementType1 ) )
870+ return 2 ; // Span<T> is better than an array type if there exists an identity conversion between the element types
871+ }
872+ return 0 ;
873+ }
874+
875+ bool IsArrayOrArrayInterfaceType ( IType type , out IType elementType )
876+ {
877+ elementType = null ;
878+ if ( type is ArrayType arrayType )
879+ {
880+ elementType = arrayType . ElementType ;
881+ return true ;
882+ }
883+ if ( type . IsArrayInterfaceType ( ) )
884+ {
885+ elementType = type . TypeArguments [ 0 ] ;
886+ return true ;
887+ }
888+ return false ;
889+ }
890+
891+ int BetterParameterPassingChoice ( Candidate c1 , Candidate c2 )
892+ {
893+ Debug . Assert ( c1 . Parameters . Count == c2 . Parameters . Count , "c1 and c2 must have the same number of parameters" ) ;
894+
895+ bool c1IsBetter = false ;
896+ bool c2IsBetter = false ;
897+
898+ for ( int i = 0 ; i < c1 . Parameters . Count ; i ++ )
899+ {
900+ ReferenceKind refKind1 = c1 . Parameters [ i ] . ReferenceKind ;
901+ ReferenceKind refKind2 = c2 . Parameters [ i ] . ReferenceKind ;
902+
903+ if ( refKind1 == ReferenceKind . None && refKind2 == ReferenceKind . In )
904+ c1IsBetter = true ; // by-value is better than in
905+ if ( refKind1 == ReferenceKind . In && refKind2 == ReferenceKind . None )
906+ c2IsBetter = true ;
907+ }
908+
909+ if ( c1IsBetter && ! c2IsBetter )
910+ return 1 ;
911+ if ( ! c1IsBetter && c2IsBetter )
912+ return 2 ;
913+
801914 return 0 ;
802915 }
803916
804917 int MoreSpecificFormalParameters ( Candidate c1 , Candidate c2 )
805918 {
806- // prefer the member with more formal parmeters (in case both have different number of optional parameters)
919+ // prefer the member with more formal parameters (in case both have different number of optional parameters)
807920 int r = c1 . Parameters . Count . CompareTo ( c2 . Parameters . Count ) ;
808921 if ( r > 0 )
809922 return 1 ;
0 commit comments