@@ -57,6 +57,9 @@ public partial class CompareInfo : IDeserializationCallback
57
57
~ ( CompareOptions . IgnoreCase | CompareOptions . IgnoreSymbols | CompareOptions . IgnoreNonSpace |
58
58
CompareOptions . IgnoreWidth | CompareOptions . IgnoreKanaType | CompareOptions . StringSort ) ;
59
59
60
+ // We cache the invariant compareinfo as we need it for OrdinalIgnoreCase hashing
61
+ internal static readonly CompareInfo Invariant = CultureInfo . InvariantCulture . CompareInfo ;
62
+
60
63
//
61
64
// CompareInfos have an interesting identity. They are attached to the locale that created them,
62
65
// ie: en-US would have an en-US sort. For haw-US (custom), then we serialize it as haw-US.
@@ -1181,21 +1184,67 @@ internal int GetHashCodeOfString(string source, CompareOptions options)
1181
1184
return GetHashCodeOfStringCore ( source , options ) ;
1182
1185
}
1183
1186
1187
+ private unsafe int GetSmallAsciiStringHash ( string source )
1188
+ {
1189
+ Debug . Assert ( source . Length <= 250 , "Input string is too long" ) ;
1190
+
1191
+ // Do not allocate on the stack if string is empty
1192
+ if ( source . Length == 0 )
1193
+ {
1194
+ return source . GetHashCode ( ) ;
1195
+ }
1196
+
1197
+ char * charArr = stackalloc char [ source . Length ] ;
1198
+ char c ;
1199
+ for ( int i = 0 ; i < source . Length ; i ++ )
1200
+ {
1201
+ c = source [ i ] ;
1202
+
1203
+ // If we have a lowercase character, ANDing off 0x20
1204
+ // will make it an uppercase character.
1205
+ if ( ( c - 'a' ) <= ( 'z' - 'a' ) )
1206
+ {
1207
+ c = ( char ) ( c & ~ 0x20 ) ;
1208
+ }
1209
+
1210
+ charArr [ i ] = c ;
1211
+ }
1212
+
1213
+ return String . InternalMarvin32HashPtr ( charArr , source . Length ) ;
1214
+ }
1215
+
1184
1216
public virtual int GetHashCode ( string source , CompareOptions options )
1185
1217
{
1186
1218
if ( source == null )
1187
1219
{
1188
1220
throw new ArgumentNullException ( nameof ( source ) ) ;
1189
1221
}
1190
1222
1223
+ if ( _invariantMode )
1224
+ {
1225
+ // If invariant mode enabled we ignore all compare options except *IgnoreCase.
1226
+ if ( ( options & ( CompareOptions . IgnoreCase | CompareOptions . OrdinalIgnoreCase ) ) != 0 )
1227
+ {
1228
+ // For small strings we allocate on the stack
1229
+ if ( source . Length <= 250 )
1230
+ return GetSmallAsciiStringHash ( source ) ;
1231
+ else
1232
+ return TextInfo . Invariant . ToUpper ( source ) . GetHashCode ( ) ;
1233
+ }
1234
+
1235
+ return source . GetHashCode ( ) ;
1236
+ }
1237
+
1191
1238
if ( options == CompareOptions . Ordinal )
1192
1239
{
1193
1240
return source . GetHashCode ( ) ;
1194
1241
}
1195
1242
1196
1243
if ( options == CompareOptions . OrdinalIgnoreCase )
1197
1244
{
1198
- return TextInfo . GetHashCodeOrdinalIgnoreCase ( source ) ;
1245
+ // We use native marvin hashing to avoid hash collisions. We are passing
1246
+ // IgnoreCase as GetHashCodeOfStringCore can't handle OrdinalIgnoreCase.
1247
+ return Invariant . GetHashCodeOfStringCore ( source , CompareOptions . IgnoreCase ) ;
1199
1248
}
1200
1249
1201
1250
//
0 commit comments