StringComparer.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using System.Globalization;
  7. using System.Runtime.Serialization;
  8. namespace System
  9. {
  10. [Serializable]
  11. [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
  12. public abstract class StringComparer : IComparer, IEqualityComparer, IComparer<string?>, IEqualityComparer<string?>
  13. {
  14. private static readonly CultureAwareComparer s_invariantCulture = new CultureAwareComparer(CultureInfo.InvariantCulture, CompareOptions.None);
  15. private static readonly CultureAwareComparer s_invariantCultureIgnoreCase = new CultureAwareComparer(CultureInfo.InvariantCulture, CompareOptions.IgnoreCase);
  16. private static readonly OrdinalCaseSensitiveComparer s_ordinal = new OrdinalCaseSensitiveComparer();
  17. private static readonly OrdinalIgnoreCaseComparer s_ordinalIgnoreCase = new OrdinalIgnoreCaseComparer();
  18. public static StringComparer InvariantCulture
  19. {
  20. get
  21. {
  22. return s_invariantCulture;
  23. }
  24. }
  25. public static StringComparer InvariantCultureIgnoreCase
  26. {
  27. get
  28. {
  29. return s_invariantCultureIgnoreCase;
  30. }
  31. }
  32. public static StringComparer CurrentCulture
  33. {
  34. get
  35. {
  36. return new CultureAwareComparer(CultureInfo.CurrentCulture, CompareOptions.None);
  37. }
  38. }
  39. public static StringComparer CurrentCultureIgnoreCase
  40. {
  41. get
  42. {
  43. return new CultureAwareComparer(CultureInfo.CurrentCulture, CompareOptions.IgnoreCase);
  44. }
  45. }
  46. public static StringComparer Ordinal
  47. {
  48. get
  49. {
  50. return s_ordinal;
  51. }
  52. }
  53. public static StringComparer OrdinalIgnoreCase
  54. {
  55. get
  56. {
  57. return s_ordinalIgnoreCase;
  58. }
  59. }
  60. // Convert a StringComparison to a StringComparer
  61. public static StringComparer FromComparison(StringComparison comparisonType)
  62. {
  63. switch (comparisonType)
  64. {
  65. case StringComparison.CurrentCulture:
  66. return CurrentCulture;
  67. case StringComparison.CurrentCultureIgnoreCase:
  68. return CurrentCultureIgnoreCase;
  69. case StringComparison.InvariantCulture:
  70. return InvariantCulture;
  71. case StringComparison.InvariantCultureIgnoreCase:
  72. return InvariantCultureIgnoreCase;
  73. case StringComparison.Ordinal:
  74. return Ordinal;
  75. case StringComparison.OrdinalIgnoreCase:
  76. return OrdinalIgnoreCase;
  77. default:
  78. throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
  79. }
  80. }
  81. public static StringComparer Create(CultureInfo culture, bool ignoreCase)
  82. {
  83. if (culture == null)
  84. {
  85. throw new ArgumentNullException(nameof(culture));
  86. }
  87. return new CultureAwareComparer(culture, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
  88. }
  89. public static StringComparer Create(CultureInfo culture, CompareOptions options)
  90. {
  91. if (culture == null)
  92. {
  93. throw new ArgumentException(nameof(culture));
  94. }
  95. return new CultureAwareComparer(culture, options);
  96. }
  97. public int Compare(object? x, object? y)
  98. {
  99. if (x == y) return 0;
  100. if (x == null) return -1;
  101. if (y == null) return 1;
  102. if (x is string sa)
  103. {
  104. if (y is string sb)
  105. {
  106. return Compare(sa, sb);
  107. }
  108. }
  109. if (x is IComparable ia)
  110. {
  111. return ia.CompareTo(y);
  112. }
  113. throw new ArgumentException(SR.Argument_ImplementIComparable);
  114. }
  115. public new bool Equals(object? x, object? y)
  116. {
  117. if (x == y) return true;
  118. if (x == null || y == null) return false;
  119. if (x is string sa)
  120. {
  121. if (y is string sb)
  122. {
  123. return Equals(sa, sb);
  124. }
  125. }
  126. return x.Equals(y);
  127. }
  128. public int GetHashCode(object obj)
  129. {
  130. if (obj == null)
  131. {
  132. throw new ArgumentNullException(nameof(obj));
  133. }
  134. if (obj is string s)
  135. {
  136. return GetHashCode(s);
  137. }
  138. return obj.GetHashCode();
  139. }
  140. public abstract int Compare(string? x, string? y);
  141. public abstract bool Equals(string? x, string? y);
  142. #pragma warning disable CS8614 // Remove warning disable when nullable attributes are respected
  143. public abstract int GetHashCode(string obj);
  144. #pragma warning restore CS8614
  145. }
  146. [Serializable]
  147. [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
  148. public sealed class CultureAwareComparer : StringComparer, ISerializable
  149. {
  150. private const CompareOptions ValidCompareMaskOffFlags = ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType | CompareOptions.StringSort);
  151. private readonly CompareInfo _compareInfo; // Do not rename (binary serialization)
  152. private CompareOptions _options;
  153. internal CultureAwareComparer(CultureInfo culture, CompareOptions options) : this(culture.CompareInfo, options) { }
  154. internal CultureAwareComparer(CompareInfo compareInfo, CompareOptions options)
  155. {
  156. _compareInfo = compareInfo;
  157. if ((options & ValidCompareMaskOffFlags) != 0)
  158. {
  159. throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
  160. }
  161. _options = options;
  162. }
  163. private CultureAwareComparer(SerializationInfo info, StreamingContext context)
  164. {
  165. _compareInfo = (CompareInfo)info.GetValue("_compareInfo", typeof(CompareInfo))!;
  166. bool ignoreCase = info.GetBoolean("_ignoreCase");
  167. var obj = info.GetValueNoThrow("_options", typeof(CompareOptions));
  168. if (obj != null)
  169. _options = (CompareOptions)obj;
  170. // fix up the _options value in case we are getting old serialized object not having _options
  171. _options |= ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;
  172. }
  173. public override int Compare(string? x, string? y)
  174. {
  175. if (object.ReferenceEquals(x, y)) return 0;
  176. if (x == null) return -1;
  177. if (y == null) return 1;
  178. return _compareInfo.Compare(x, y, _options);
  179. }
  180. public override bool Equals(string? x, string? y)
  181. {
  182. if (object.ReferenceEquals(x, y)) return true;
  183. if (x == null || y == null) return false;
  184. return _compareInfo.Compare(x, y, _options) == 0;
  185. }
  186. public override int GetHashCode(string obj)
  187. {
  188. if (obj == null)
  189. {
  190. throw new ArgumentNullException(nameof(obj));
  191. }
  192. return _compareInfo.GetHashCodeOfString(obj, _options);
  193. }
  194. // Equals method for the comparer itself.
  195. public override bool Equals(object? obj)
  196. {
  197. return
  198. obj is CultureAwareComparer comparer &&
  199. _options == comparer._options &&
  200. _compareInfo.Equals(comparer._compareInfo);
  201. }
  202. public override int GetHashCode()
  203. {
  204. return _compareInfo.GetHashCode() ^ ((int)_options & 0x7FFFFFFF);
  205. }
  206. public void GetObjectData(SerializationInfo info, StreamingContext context)
  207. {
  208. info.AddValue("_compareInfo", _compareInfo);
  209. info.AddValue("_options", _options);
  210. info.AddValue("_ignoreCase", (_options & CompareOptions.IgnoreCase) != 0);
  211. }
  212. }
  213. [Serializable]
  214. [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
  215. public class OrdinalComparer : StringComparer
  216. {
  217. private readonly bool _ignoreCase; // Do not rename (binary serialization)
  218. internal OrdinalComparer(bool ignoreCase)
  219. {
  220. _ignoreCase = ignoreCase;
  221. }
  222. public override int Compare(string? x, string? y)
  223. {
  224. if (ReferenceEquals(x, y))
  225. return 0;
  226. if (x == null)
  227. return -1;
  228. if (y == null)
  229. return 1;
  230. if (_ignoreCase)
  231. {
  232. return string.Compare(x, y, StringComparison.OrdinalIgnoreCase);
  233. }
  234. return string.CompareOrdinal(x, y);
  235. }
  236. public override bool Equals(string? x, string? y)
  237. {
  238. if (ReferenceEquals(x, y))
  239. return true;
  240. if (x == null || y == null)
  241. return false;
  242. if (_ignoreCase)
  243. {
  244. if (x.Length != y.Length)
  245. {
  246. return false;
  247. }
  248. return CompareInfo.EqualsOrdinalIgnoreCase(ref x.GetRawStringData(), ref y.GetRawStringData(), x.Length);
  249. }
  250. return x.Equals(y);
  251. }
  252. public override int GetHashCode(string obj)
  253. {
  254. if (obj == null)
  255. {
  256. ThrowHelper.ThrowArgumentNullException(ExceptionArgument.obj);
  257. }
  258. if (_ignoreCase)
  259. {
  260. return obj.GetHashCodeOrdinalIgnoreCase();
  261. }
  262. return obj.GetHashCode();
  263. }
  264. // Equals method for the comparer itself.
  265. public override bool Equals(object? obj)
  266. {
  267. if (!(obj is OrdinalComparer comparer))
  268. {
  269. return false;
  270. }
  271. return (this._ignoreCase == comparer._ignoreCase);
  272. }
  273. public override int GetHashCode()
  274. {
  275. int hashCode = nameof(OrdinalComparer).GetHashCode();
  276. return _ignoreCase ? (~hashCode) : hashCode;
  277. }
  278. }
  279. [Serializable]
  280. internal sealed class OrdinalCaseSensitiveComparer : OrdinalComparer, ISerializable
  281. {
  282. public OrdinalCaseSensitiveComparer() : base(false)
  283. {
  284. }
  285. public override int Compare(string? x, string? y) => string.CompareOrdinal(x, y);
  286. public override bool Equals(string? x, string? y) => string.Equals(x, y);
  287. public override int GetHashCode(string obj)
  288. {
  289. if (obj == null)
  290. {
  291. ThrowHelper.ThrowArgumentNullException(ExceptionArgument.obj);
  292. }
  293. return obj.GetHashCode();
  294. }
  295. public void GetObjectData(SerializationInfo info, StreamingContext context)
  296. {
  297. info.SetType(typeof(OrdinalComparer));
  298. info.AddValue("_ignoreCase", false);
  299. }
  300. }
  301. [Serializable]
  302. internal sealed class OrdinalIgnoreCaseComparer : OrdinalComparer, ISerializable
  303. {
  304. public OrdinalIgnoreCaseComparer() : base(true)
  305. {
  306. }
  307. public override int Compare(string? x, string? y) => string.Compare(x, y, StringComparison.OrdinalIgnoreCase);
  308. public override bool Equals(string? x, string? y)
  309. {
  310. if (ReferenceEquals(x, y))
  311. {
  312. return true;
  313. }
  314. if (x is null || y is null)
  315. {
  316. return false;
  317. }
  318. if (x.Length != y.Length)
  319. {
  320. return false;
  321. }
  322. return CompareInfo.EqualsOrdinalIgnoreCase(ref x.GetRawStringData(), ref y.GetRawStringData(), x.Length);
  323. }
  324. public override int GetHashCode(string obj)
  325. {
  326. if (obj == null)
  327. {
  328. ThrowHelper.ThrowArgumentNullException(ExceptionArgument.obj);
  329. }
  330. return obj.GetHashCodeOrdinalIgnoreCase();
  331. }
  332. public void GetObjectData(SerializationInfo info, StreamingContext context)
  333. {
  334. info.SetType(typeof(OrdinalComparer));
  335. info.AddValue("_ignoreCase", true);
  336. }
  337. }
  338. }