StringComparer.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  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. public abstract int GetHashCode(string obj);
  143. }
  144. [Serializable]
  145. [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
  146. public sealed class CultureAwareComparer : StringComparer, ISerializable
  147. {
  148. private const CompareOptions ValidCompareMaskOffFlags = ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreWidth | CompareOptions.IgnoreKanaType | CompareOptions.StringSort);
  149. private readonly CompareInfo _compareInfo; // Do not rename (binary serialization)
  150. private CompareOptions _options;
  151. internal CultureAwareComparer(CultureInfo culture, CompareOptions options) : this(culture.CompareInfo, options) { }
  152. internal CultureAwareComparer(CompareInfo compareInfo, CompareOptions options)
  153. {
  154. _compareInfo = compareInfo;
  155. if ((options & ValidCompareMaskOffFlags) != 0)
  156. {
  157. throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
  158. }
  159. _options = options;
  160. }
  161. private CultureAwareComparer(SerializationInfo info, StreamingContext context)
  162. {
  163. _compareInfo = (CompareInfo)info.GetValue("_compareInfo", typeof(CompareInfo));
  164. bool ignoreCase = info.GetBoolean("_ignoreCase");
  165. var obj = info.GetValueNoThrow("_options", typeof(CompareOptions));
  166. if (obj != null)
  167. _options = (CompareOptions)obj;
  168. // fix up the _options value in case we are getting old serialized object not having _options
  169. _options |= ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;
  170. }
  171. public override int Compare(string x, string y)
  172. {
  173. if (object.ReferenceEquals(x, y)) return 0;
  174. if (x == null) return -1;
  175. if (y == null) return 1;
  176. return _compareInfo.Compare(x, y, _options);
  177. }
  178. public override bool Equals(string x, string y)
  179. {
  180. if (object.ReferenceEquals(x, y)) return true;
  181. if (x == null || y == null) return false;
  182. return _compareInfo.Compare(x, y, _options) == 0;
  183. }
  184. public override int GetHashCode(string obj)
  185. {
  186. if (obj == null)
  187. {
  188. throw new ArgumentNullException(nameof(obj));
  189. }
  190. return _compareInfo.GetHashCodeOfString(obj, _options);
  191. }
  192. // Equals method for the comparer itself.
  193. public override bool Equals(object obj)
  194. {
  195. return
  196. obj is CultureAwareComparer comparer &&
  197. _options == comparer._options &&
  198. _compareInfo.Equals(comparer._compareInfo);
  199. }
  200. public override int GetHashCode()
  201. {
  202. return _compareInfo.GetHashCode() ^ ((int)_options & 0x7FFFFFFF);
  203. }
  204. public void GetObjectData(SerializationInfo info, StreamingContext context)
  205. {
  206. info.AddValue("_compareInfo", _compareInfo);
  207. info.AddValue("_options", _options);
  208. info.AddValue("_ignoreCase", (_options & CompareOptions.IgnoreCase) != 0);
  209. }
  210. }
  211. [Serializable]
  212. [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
  213. public class OrdinalComparer : StringComparer
  214. {
  215. private readonly bool _ignoreCase; // Do not rename (binary serialization)
  216. internal OrdinalComparer(bool ignoreCase)
  217. {
  218. _ignoreCase = ignoreCase;
  219. }
  220. public override int Compare(string x, string y)
  221. {
  222. if (ReferenceEquals(x, y))
  223. return 0;
  224. if (x == null)
  225. return -1;
  226. if (y == null)
  227. return 1;
  228. if (_ignoreCase)
  229. {
  230. return string.Compare(x, y, StringComparison.OrdinalIgnoreCase);
  231. }
  232. return string.CompareOrdinal(x, y);
  233. }
  234. public override bool Equals(string x, string y)
  235. {
  236. if (ReferenceEquals(x, y))
  237. return true;
  238. if (x == null || y == null)
  239. return false;
  240. if (_ignoreCase)
  241. {
  242. if (x.Length != y.Length)
  243. {
  244. return false;
  245. }
  246. return CompareInfo.EqualsOrdinalIgnoreCase(ref x.GetRawStringData(), ref y.GetRawStringData(), x.Length);
  247. }
  248. return x.Equals(y);
  249. }
  250. public override int GetHashCode(string obj)
  251. {
  252. if (obj == null)
  253. {
  254. ThrowHelper.ThrowArgumentNullException(ExceptionArgument.obj);
  255. }
  256. if (_ignoreCase)
  257. {
  258. return obj.GetHashCodeOrdinalIgnoreCase();
  259. }
  260. return obj.GetHashCode();
  261. }
  262. // Equals method for the comparer itself.
  263. public override bool Equals(object obj)
  264. {
  265. if (!(obj is OrdinalComparer comparer))
  266. {
  267. return false;
  268. }
  269. return (this._ignoreCase == comparer._ignoreCase);
  270. }
  271. public override int GetHashCode()
  272. {
  273. int hashCode = nameof(OrdinalComparer).GetHashCode();
  274. return _ignoreCase ? (~hashCode) : hashCode;
  275. }
  276. }
  277. [Serializable]
  278. internal sealed class OrdinalCaseSensitiveComparer : OrdinalComparer, ISerializable
  279. {
  280. public OrdinalCaseSensitiveComparer() : base(false)
  281. {
  282. }
  283. public override int Compare(string x, string y) => string.CompareOrdinal(x, y);
  284. public override bool Equals(string x, string y) => string.Equals(x, y);
  285. public override int GetHashCode(string obj)
  286. {
  287. if (obj == null)
  288. {
  289. ThrowHelper.ThrowArgumentNullException(ExceptionArgument.obj);
  290. }
  291. return obj.GetHashCode();
  292. }
  293. public void GetObjectData(SerializationInfo info, StreamingContext context)
  294. {
  295. info.SetType(typeof(OrdinalComparer));
  296. info.AddValue("_ignoreCase", false);
  297. }
  298. }
  299. [Serializable]
  300. internal sealed class OrdinalIgnoreCaseComparer : OrdinalComparer, ISerializable
  301. {
  302. public OrdinalIgnoreCaseComparer() : base(true)
  303. {
  304. }
  305. public override int Compare(string x, string y) => string.Compare(x, y, StringComparison.OrdinalIgnoreCase);
  306. public override bool Equals(string x, string y)
  307. {
  308. if (ReferenceEquals(x, y))
  309. {
  310. return true;
  311. }
  312. if (x is null || y is null)
  313. {
  314. return false;
  315. }
  316. if (x.Length != y.Length)
  317. {
  318. return false;
  319. }
  320. return CompareInfo.EqualsOrdinalIgnoreCase(ref x.GetRawStringData(), ref y.GetRawStringData(), x.Length);
  321. }
  322. public override int GetHashCode(string obj)
  323. {
  324. if (obj == null)
  325. {
  326. ThrowHelper.ThrowArgumentNullException(ExceptionArgument.obj);
  327. }
  328. return obj.GetHashCodeOrdinalIgnoreCase();
  329. }
  330. public void GetObjectData(SerializationInfo info, StreamingContext context)
  331. {
  332. info.SetType(typeof(OrdinalComparer));
  333. info.AddValue("_ignoreCase", true);
  334. }
  335. }
  336. }