StringComparer.cs 12 KB

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