11using System ;
2+ using System . Collections ;
23using System . Collections . Generic ;
4+ using System . Reflection ;
5+ using BenchmarkDotNet . Portability ;
6+
7+ #if NETSTANDARD2_0
8+ using System . Collections . Concurrent ;
9+ using System . Linq ;
10+ #endif
311
412namespace BenchmarkDotNet . Parameters
513{
614 internal class ParameterComparer : IComparer < ParameterInstances >
715 {
16+ #if NETSTANDARD2_0
17+ private static readonly ConcurrentDictionary < Type , MemberInfo [ ] > s_tupleMembersCache = new ( ) ;
18+
19+ private static MemberInfo [ ] GetTupleMembers ( Type type )
20+ => s_tupleMembersCache . GetOrAdd ( type , t =>
21+ {
22+ var members = type . FullName . StartsWith ( "System.Tuple`" )
23+ ? type . GetProperties ( BindingFlags . Public | BindingFlags . Instance ) . Cast < MemberInfo > ( )
24+ : type . GetFields ( BindingFlags . Public | BindingFlags . Instance ) . Cast < MemberInfo > ( ) ;
25+ return members
26+ . Where ( p => p . Name . StartsWith ( "Item" ) )
27+ . OrderBy ( p => p . Name )
28+ . ToArray ( ) ;
29+ } ) ;
30+ #endif
31+
832 public static readonly ParameterComparer Instance = new ParameterComparer ( ) ;
933
1034 public int Compare ( ParameterInstances x , ParameterInstances y )
1135 {
1236 if ( x == null && y == null ) return 0 ;
13- if ( x != null && y == null ) return 1 ;
1437 if ( x == null ) return - 1 ;
38+ if ( y == null ) return 1 ;
39+
1540 for ( int i = 0 ; i < Math . Min ( x . Count , y . Count ) ; i ++ )
1641 {
17- var compareTo = CompareValues ( x [ i ] ? . Value , y [ i ] ? . Value ) ;
18- if ( compareTo != 0 )
19- return compareTo ;
42+ var comparison = CompareValues ( x [ i ] ? . Value , y [ i ] ? . Value ) ;
43+ if ( comparison != 0 )
44+ {
45+ return comparison ;
46+ }
2047 }
48+
2149 return string . CompareOrdinal ( x . DisplayInfo , y . DisplayInfo ) ;
2250 }
2351
24- private int CompareValues ( object x , object y )
52+ private static int CompareValues < T1 , T2 > ( T1 x , T2 y )
2553 {
26- // Detect IComparable implementations.
27- // This works for all primitive types in addition to user types that implement IComparable.
28- if ( x != null && y != null && x . GetType ( ) == y . GetType ( ) &&
29- x is IComparable xComparable )
54+ if ( x == null || y == null || x . GetType ( ) != y . GetType ( ) )
55+ {
56+ return string . CompareOrdinal ( x ? . ToString ( ) , y ? . ToString ( ) ) ;
57+ }
58+
59+ if ( x is IStructuralComparable xStructuralComparable )
3060 {
3161 try
3262 {
33- return xComparable . CompareTo ( y ) ;
63+ return StructuralComparisons . StructuralComparer . Compare ( x , y ) ;
3464 }
35- // Some types, such as Tuple and ValueTuple, have a fallible CompareTo implementation which can throw if the inner items don't implement IComparable.
36- // See: https://github.com/dotnet/BenchmarkDotNet /issues/2346
37- // For now, catch and ignore the exception, and fallback to string comparison below .
38- catch ( ArgumentException ex ) when ( ex . Message . Contains ( "At least one object must implement IComparable." ) )
65+ // https://github.com/dotnet/BenchmarkDotNet/issues/2346
66+ // https://github.com/dotnet/runtime /issues/66472
67+ // Unfortunately we can't rely on checking the exception message because it may change per current culture .
68+ catch ( ArgumentException )
3969 {
70+ if ( TryFallbackStructuralCompareTo ( x , y , out int comparison ) )
71+ {
72+ return comparison ;
73+ }
74+ // A complex user type did not handle a multi-dimensional array or tuple, just re-throw.
75+ throw ;
4076 }
4177 }
4278
43- // Anything else.
79+ if ( x is IComparable xComparable )
80+ {
81+ // Tuples are already handled by IStructuralComparable case, if this throws, it's the user's own fault.
82+ return xComparable . CompareTo ( y ) ;
83+ }
84+
85+ if ( x is IEnumerable xEnumerable ) // General collection comparison support
86+ {
87+ return CompareEnumerables ( xEnumerable , ( IEnumerable ) y ) ;
88+ }
89+
90+ // Anything else to differentiate between objects.
4491 return string . CompareOrdinal ( x ? . ToString ( ) , y ? . ToString ( ) ) ;
4592 }
93+
94+ private static bool TryFallbackStructuralCompareTo ( object x , object y , out int comparison )
95+ {
96+ // Check for multi-dimensional array and ITuple and re-try for each element recursively.
97+ if ( x is Array xArr )
98+ {
99+ Array yArr = ( Array ) y ;
100+ if ( xArr . Rank != yArr . Rank )
101+ {
102+ comparison = xArr . Rank . CompareTo ( yArr . Rank ) ;
103+ return true ;
104+ }
105+
106+ for ( int dim = 0 ; dim < xArr . Rank ; dim ++ )
107+ {
108+ if ( xArr . GetLength ( dim ) != yArr . GetLength ( dim ) )
109+ {
110+ comparison = xArr . GetLength ( dim ) . CompareTo ( yArr . GetLength ( dim ) ) ;
111+ return true ;
112+ }
113+ }
114+
115+ // Common 2D and 3D arrays are specialized to avoid expensive boxing where possible.
116+ if ( ! RuntimeInformation . IsAot && xArr . Rank is 2 or 3 )
117+ {
118+ string methodName = xArr . Rank == 2
119+ ? nameof ( CompareTwoDArray )
120+ : nameof ( CompareThreeDArray ) ;
121+ comparison = ( int ) typeof ( ParameterComparer )
122+ . GetMethod ( methodName , BindingFlags . NonPublic | BindingFlags . Static )
123+ . MakeGenericMethod ( xArr . GetType ( ) . GetElementType ( ) , yArr . GetType ( ) . GetElementType ( ) )
124+ . Invoke ( null , [ xArr , yArr ] ) ;
125+ return true ;
126+ }
127+
128+ // 1D arrays will only hit this code path if a nested type is a multi-dimensional array.
129+ // 4D and larger fall back to enumerable.
130+ comparison = CompareEnumerables ( xArr , yArr ) ;
131+ return true ;
132+ }
133+
134+ #if NETSTANDARD2_0
135+ // ITuple does not exist in netstandard2.0, so we have to use reflection. ITuple does exist in net471 and newer, but the System.ValueTuple nuget package does not implement it.
136+ string typeName = x . GetType ( ) . FullName ;
137+ if ( typeName . StartsWith ( "System.Tuple`" ) )
138+ {
139+ comparison = CompareTuples ( x , y ) ;
140+ return true ;
141+ }
142+ else if ( typeName . StartsWith ( "System.ValueTuple`" ) )
143+ {
144+ comparison = CompareValueTuples ( x , y ) ;
145+ return true ;
146+ }
147+ #else
148+ if ( x is System . Runtime . CompilerServices . ITuple xTuple )
149+ {
150+ comparison = CompareTuples ( xTuple , ( System . Runtime . CompilerServices . ITuple ) y ) ;
151+ return true ;
152+ }
153+ #endif
154+
155+ if ( x is IEnumerable xEnumerable ) // General collection equality support
156+ {
157+ comparison = CompareEnumerables ( xEnumerable , ( IEnumerable ) y ) ;
158+ return true ;
159+ }
160+
161+ comparison = 0 ;
162+ return false ;
163+ }
164+
165+ private static int CompareEnumerables ( IEnumerable x , IEnumerable y )
166+ {
167+ var xEnumerator = x . GetEnumerator ( ) ;
168+ try
169+ {
170+ var yEnumerator = y . GetEnumerator ( ) ;
171+ try
172+ {
173+ while ( xEnumerator . MoveNext ( ) )
174+ {
175+ if ( ! yEnumerator . MoveNext ( ) )
176+ {
177+ return - 1 ;
178+ }
179+ int comparison = CompareValues ( xEnumerator . Current , yEnumerator . Current ) ;
180+ if ( comparison != 0 )
181+ {
182+ return comparison ;
183+ }
184+ }
185+ return yEnumerator . MoveNext ( ) ? 1 : 0 ;
186+ }
187+ finally
188+ {
189+ if ( yEnumerator is IDisposable disposable )
190+ {
191+ disposable . Dispose ( ) ;
192+ }
193+ }
194+ }
195+ finally
196+ {
197+ if ( xEnumerator is IDisposable disposable )
198+ {
199+ disposable . Dispose ( ) ;
200+ }
201+ }
202+ }
203+
204+ private static int CompareTwoDArray < T1 , T2 > ( T1 [ , ] arrOne , T2 [ , ] arrTwo )
205+ {
206+ // Assumes that arrOne & arrTwo are the same length and width.
207+ for ( int i = 0 ; i < arrOne . GetLength ( 0 ) ; i ++ )
208+ {
209+ for ( int j = 0 ; j < arrOne . GetLength ( 1 ) ; j ++ )
210+ {
211+ var comparison = CompareValues ( arrOne [ i , j ] , arrTwo [ i , j ] ) ;
212+ if ( comparison != 0 )
213+ {
214+ return comparison ;
215+ }
216+ }
217+ }
218+
219+ return 0 ;
220+ }
221+
222+ private static int CompareThreeDArray < T1 , T2 > ( T1 [ , , ] arrOne , T2 [ , , ] arrTwo )
223+ {
224+ // Assumes that arrOne & arrTwo are the same length, width, and height.
225+ for ( int i = 0 ; i < arrOne . GetLength ( 0 ) ; i ++ )
226+ {
227+ for ( int j = 0 ; j < arrOne . GetLength ( 1 ) ; j ++ )
228+ {
229+ for ( int k = 0 ; k < arrOne . GetLength ( 2 ) ; k ++ )
230+ {
231+ var comparison = CompareValues ( arrOne [ i , j , k ] , arrTwo [ i , j , k ] ) ;
232+ if ( comparison != 0 )
233+ {
234+ return comparison ;
235+ }
236+ }
237+ }
238+ }
239+
240+ return 0 ;
241+ }
242+
243+ #if NETSTANDARD2_0
244+ private static int CompareTuples ( object x , object y )
245+ {
246+ foreach ( PropertyInfo property in GetTupleMembers ( x . GetType ( ) ) )
247+ {
248+ var comparison = CompareValues ( property . GetValue ( x ) , property . GetValue ( y ) ) ;
249+ if ( comparison != 0 )
250+ {
251+ return comparison ;
252+ }
253+ }
254+
255+ return 0 ;
256+ }
257+
258+ private static int CompareValueTuples ( object x , object y )
259+ {
260+ foreach ( FieldInfo field in GetTupleMembers ( x . GetType ( ) ) )
261+ {
262+ var comparison = CompareValues ( field . GetValue ( x ) , field . GetValue ( y ) ) ;
263+ if ( comparison != 0 )
264+ {
265+ return comparison ;
266+ }
267+ }
268+
269+ return 0 ;
270+ }
271+ #else
272+ private static int CompareTuples ( System . Runtime . CompilerServices . ITuple x , System . Runtime . CompilerServices . ITuple y )
273+ {
274+ for ( int i = 0 ; i < x . Length ; i ++ )
275+ {
276+ var comparison = CompareValues ( x [ i ] , y [ i ] ) ;
277+ if ( comparison != 0 )
278+ {
279+ return comparison ;
280+ }
281+ }
282+
283+ return 0 ;
284+ }
285+ #endif
46286 }
47287}
0 commit comments