Version.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  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.Globalization;
  5. using System.Diagnostics;
  6. using System.Text;
  7. using System.Runtime.CompilerServices;
  8. namespace System
  9. {
  10. // A Version object contains four hierarchical numeric components: major, minor,
  11. // build and revision. Build and revision may be unspecified, which is represented
  12. // internally as a -1. By definition, an unspecified component matches anything
  13. // (both unspecified and specified), and an unspecified component is "less than" any
  14. // specified component.
  15. [Serializable]
  16. [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
  17. public sealed class Version : ICloneable, IComparable, IComparable<Version>, IEquatable<Version>, ISpanFormattable
  18. {
  19. // AssemblyName depends on the order staying the same
  20. private readonly int _Major; // Do not rename (binary serialization)
  21. private readonly int _Minor; // Do not rename (binary serialization)
  22. private readonly int _Build = -1; // Do not rename (binary serialization)
  23. private readonly int _Revision = -1; // Do not rename (binary serialization)
  24. public Version(int major, int minor, int build, int revision)
  25. {
  26. if (major < 0)
  27. throw new ArgumentOutOfRangeException(nameof(major), SR.ArgumentOutOfRange_Version);
  28. if (minor < 0)
  29. throw new ArgumentOutOfRangeException(nameof(minor), SR.ArgumentOutOfRange_Version);
  30. if (build < 0)
  31. throw new ArgumentOutOfRangeException(nameof(build), SR.ArgumentOutOfRange_Version);
  32. if (revision < 0)
  33. throw new ArgumentOutOfRangeException(nameof(revision), SR.ArgumentOutOfRange_Version);
  34. _Major = major;
  35. _Minor = minor;
  36. _Build = build;
  37. _Revision = revision;
  38. }
  39. public Version(int major, int minor, int build)
  40. {
  41. if (major < 0)
  42. throw new ArgumentOutOfRangeException(nameof(major), SR.ArgumentOutOfRange_Version);
  43. if (minor < 0)
  44. throw new ArgumentOutOfRangeException(nameof(minor), SR.ArgumentOutOfRange_Version);
  45. if (build < 0)
  46. throw new ArgumentOutOfRangeException(nameof(build), SR.ArgumentOutOfRange_Version);
  47. _Major = major;
  48. _Minor = minor;
  49. _Build = build;
  50. }
  51. public Version(int major, int minor)
  52. {
  53. if (major < 0)
  54. throw new ArgumentOutOfRangeException(nameof(major), SR.ArgumentOutOfRange_Version);
  55. if (minor < 0)
  56. throw new ArgumentOutOfRangeException(nameof(minor), SR.ArgumentOutOfRange_Version);
  57. _Major = major;
  58. _Minor = minor;
  59. }
  60. public Version(string version)
  61. {
  62. Version v = Version.Parse(version);
  63. _Major = v.Major;
  64. _Minor = v.Minor;
  65. _Build = v.Build;
  66. _Revision = v.Revision;
  67. }
  68. public Version()
  69. {
  70. _Major = 0;
  71. _Minor = 0;
  72. }
  73. private Version(Version version)
  74. {
  75. Debug.Assert(version != null);
  76. _Major = version._Major;
  77. _Minor = version._Minor;
  78. _Build = version._Build;
  79. _Revision = version._Revision;
  80. }
  81. public object Clone()
  82. {
  83. return new Version(this);
  84. }
  85. // Properties for setting and getting version numbers
  86. public int Major
  87. {
  88. get { return _Major; }
  89. }
  90. public int Minor
  91. {
  92. get { return _Minor; }
  93. }
  94. public int Build
  95. {
  96. get { return _Build; }
  97. }
  98. public int Revision
  99. {
  100. get { return _Revision; }
  101. }
  102. public short MajorRevision
  103. {
  104. get { return (short)(_Revision >> 16); }
  105. }
  106. public short MinorRevision
  107. {
  108. get { return (short)(_Revision & 0xFFFF); }
  109. }
  110. public int CompareTo(object version)
  111. {
  112. if (version == null)
  113. {
  114. return 1;
  115. }
  116. Version v = version as Version;
  117. if (v == null)
  118. {
  119. throw new ArgumentException(SR.Arg_MustBeVersion);
  120. }
  121. return CompareTo(v);
  122. }
  123. public int CompareTo(Version value)
  124. {
  125. return
  126. object.ReferenceEquals(value, this) ? 0 :
  127. value is null ? 1 :
  128. _Major != value._Major ? (_Major > value._Major ? 1 : -1) :
  129. _Minor != value._Minor ? (_Minor > value._Minor ? 1 : -1) :
  130. _Build != value._Build ? (_Build > value._Build ? 1 : -1) :
  131. _Revision != value._Revision ? (_Revision > value._Revision ? 1 : -1) :
  132. 0;
  133. }
  134. public override bool Equals(object obj)
  135. {
  136. return Equals(obj as Version);
  137. }
  138. public bool Equals(Version obj)
  139. {
  140. return object.ReferenceEquals(obj, this) ||
  141. (!(obj is null) &&
  142. _Major == obj._Major &&
  143. _Minor == obj._Minor &&
  144. _Build == obj._Build &&
  145. _Revision == obj._Revision);
  146. }
  147. public override int GetHashCode()
  148. {
  149. // Let's assume that most version numbers will be pretty small and just
  150. // OR some lower order bits together.
  151. int accumulator = 0;
  152. accumulator |= (_Major & 0x0000000F) << 28;
  153. accumulator |= (_Minor & 0x000000FF) << 20;
  154. accumulator |= (_Build & 0x000000FF) << 12;
  155. accumulator |= (_Revision & 0x00000FFF);
  156. return accumulator;
  157. }
  158. public override string ToString() =>
  159. ToString(DefaultFormatFieldCount);
  160. public string ToString(int fieldCount) =>
  161. fieldCount == 0 ? string.Empty :
  162. fieldCount == 1 ? _Major.ToString() :
  163. StringBuilderCache.GetStringAndRelease(ToCachedStringBuilder(fieldCount));
  164. public bool TryFormat(Span<char> destination, out int charsWritten) =>
  165. TryFormat(destination, DefaultFormatFieldCount, out charsWritten);
  166. public bool TryFormat(Span<char> destination, int fieldCount, out int charsWritten)
  167. {
  168. if (fieldCount == 0)
  169. {
  170. charsWritten = 0;
  171. return true;
  172. }
  173. else if (fieldCount == 1)
  174. {
  175. return _Major.TryFormat(destination, out charsWritten);
  176. }
  177. StringBuilder sb = ToCachedStringBuilder(fieldCount);
  178. if (sb.Length <= destination.Length)
  179. {
  180. sb.CopyTo(0, destination, sb.Length);
  181. StringBuilderCache.Release(sb);
  182. charsWritten = sb.Length;
  183. return true;
  184. }
  185. StringBuilderCache.Release(sb);
  186. charsWritten = 0;
  187. return false;
  188. }
  189. bool ISpanFormattable.TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider provider)
  190. {
  191. // format and provider are ignored.
  192. return TryFormat(destination, out charsWritten);
  193. }
  194. private int DefaultFormatFieldCount =>
  195. _Build == -1 ? 2 :
  196. _Revision == -1 ? 3 :
  197. 4;
  198. private StringBuilder ToCachedStringBuilder(int fieldCount)
  199. {
  200. // Note: As we always have positive numbers then it is safe to convert the number to string
  201. // regardless of the current culture as we'll not have any punctuation marks in the number.
  202. if (fieldCount == 2)
  203. {
  204. StringBuilder sb = StringBuilderCache.Acquire();
  205. sb.Append(_Major);
  206. sb.Append('.');
  207. sb.Append(_Minor);
  208. return sb;
  209. }
  210. else
  211. {
  212. if (_Build == -1)
  213. {
  214. throw new ArgumentException(SR.Format(SR.ArgumentOutOfRange_Bounds_Lower_Upper, "0", "2"), nameof(fieldCount));
  215. }
  216. if (fieldCount == 3)
  217. {
  218. StringBuilder sb = StringBuilderCache.Acquire();
  219. sb.Append(_Major);
  220. sb.Append('.');
  221. sb.Append(_Minor);
  222. sb.Append('.');
  223. sb.Append(_Build);
  224. return sb;
  225. }
  226. if (_Revision == -1)
  227. {
  228. throw new ArgumentException(SR.Format(SR.ArgumentOutOfRange_Bounds_Lower_Upper, "0", "3"), nameof(fieldCount));
  229. }
  230. if (fieldCount == 4)
  231. {
  232. StringBuilder sb = StringBuilderCache.Acquire();
  233. sb.Append(_Major);
  234. sb.Append('.');
  235. sb.Append(_Minor);
  236. sb.Append('.');
  237. sb.Append(_Build);
  238. sb.Append('.');
  239. sb.Append(_Revision);
  240. return sb;
  241. }
  242. throw new ArgumentException(SR.Format(SR.ArgumentOutOfRange_Bounds_Lower_Upper, "0", "4"), nameof(fieldCount));
  243. }
  244. }
  245. public static Version Parse(string input)
  246. {
  247. if (input == null)
  248. {
  249. throw new ArgumentNullException(nameof(input));
  250. }
  251. return ParseVersion(input.AsSpan(), throwOnFailure: true);
  252. }
  253. public static Version Parse(ReadOnlySpan<char> input) =>
  254. ParseVersion(input, throwOnFailure: true);
  255. public static bool TryParse(string input, out Version result)
  256. {
  257. if (input == null)
  258. {
  259. result = null;
  260. return false;
  261. }
  262. return (result = ParseVersion(input.AsSpan(), throwOnFailure: false)) != null;
  263. }
  264. public static bool TryParse(ReadOnlySpan<char> input, out Version result) =>
  265. (result = ParseVersion(input, throwOnFailure: false)) != null;
  266. private static Version ParseVersion(ReadOnlySpan<char> input, bool throwOnFailure)
  267. {
  268. // Find the separator between major and minor. It must exist.
  269. int majorEnd = input.IndexOf('.');
  270. if (majorEnd < 0)
  271. {
  272. if (throwOnFailure) throw new ArgumentException(SR.Arg_VersionString, nameof(input));
  273. return null;
  274. }
  275. // Find the ends of the optional minor and build portions.
  276. // We musn't have any separators after build.
  277. int buildEnd = -1;
  278. int minorEnd = input.Slice(majorEnd + 1).IndexOf('.');
  279. if (minorEnd != -1)
  280. {
  281. minorEnd += (majorEnd + 1);
  282. buildEnd = input.Slice(minorEnd + 1).IndexOf('.');
  283. if (buildEnd != -1)
  284. {
  285. buildEnd += (minorEnd + 1);
  286. if (input.Slice(buildEnd + 1).Contains('.'))
  287. {
  288. if (throwOnFailure) throw new ArgumentException(SR.Arg_VersionString, nameof(input));
  289. return null;
  290. }
  291. }
  292. }
  293. int minor, build, revision;
  294. // Parse the major version
  295. if (!TryParseComponent(input.Slice(0, majorEnd), nameof(input), throwOnFailure, out int major))
  296. {
  297. return null;
  298. }
  299. if (minorEnd != -1)
  300. {
  301. // If there's more than a major and minor, parse the minor, too.
  302. if (!TryParseComponent(input.Slice(majorEnd + 1, minorEnd - majorEnd - 1), nameof(input), throwOnFailure, out minor))
  303. {
  304. return null;
  305. }
  306. if (buildEnd != -1)
  307. {
  308. // major.minor.build.revision
  309. return
  310. TryParseComponent(input.Slice(minorEnd + 1, buildEnd - minorEnd - 1), nameof(build), throwOnFailure, out build) &&
  311. TryParseComponent(input.Slice(buildEnd + 1), nameof(revision), throwOnFailure, out revision) ?
  312. new Version(major, minor, build, revision) :
  313. null;
  314. }
  315. else
  316. {
  317. // major.minor.build
  318. return TryParseComponent(input.Slice(minorEnd + 1), nameof(build), throwOnFailure, out build) ?
  319. new Version(major, minor, build) :
  320. null;
  321. }
  322. }
  323. else
  324. {
  325. // major.minor
  326. return TryParseComponent(input.Slice(majorEnd + 1), nameof(input), throwOnFailure, out minor) ?
  327. new Version(major, minor) :
  328. null;
  329. }
  330. }
  331. private static bool TryParseComponent(ReadOnlySpan<char> component, string componentName, bool throwOnFailure, out int parsedComponent)
  332. {
  333. if (throwOnFailure)
  334. {
  335. if ((parsedComponent = int.Parse(component, NumberStyles.Integer, CultureInfo.InvariantCulture)) < 0)
  336. {
  337. throw new ArgumentOutOfRangeException(componentName, SR.ArgumentOutOfRange_Version);
  338. }
  339. return true;
  340. }
  341. return int.TryParse(component, NumberStyles.Integer, CultureInfo.InvariantCulture, out parsedComponent) && parsedComponent >= 0;
  342. }
  343. // Force inline as the true/false ternary takes it above ALWAYS_INLINE size even though the asm ends up smaller
  344. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  345. public static bool operator ==(Version v1, Version v2)
  346. {
  347. // Test "right" first to allow branch elimination when inlined for null checks (== null)
  348. // so it can become a simple test
  349. if (v2 is null)
  350. {
  351. // return true/false not the test result https://github.com/dotnet/coreclr/issues/914
  352. return (v1 is null) ? true : false;
  353. }
  354. // Quick reference equality test prior to calling the virtual Equality
  355. return ReferenceEquals(v2, v1) ? true : v2.Equals(v1);
  356. }
  357. public static bool operator !=(Version v1, Version v2)
  358. {
  359. return !(v1 == v2);
  360. }
  361. public static bool operator <(Version v1, Version v2)
  362. {
  363. if ((object)v1 == null)
  364. throw new ArgumentNullException(nameof(v1));
  365. return (v1.CompareTo(v2) < 0);
  366. }
  367. public static bool operator <=(Version v1, Version v2)
  368. {
  369. if ((object)v1 == null)
  370. throw new ArgumentNullException(nameof(v1));
  371. return (v1.CompareTo(v2) <= 0);
  372. }
  373. public static bool operator >(Version v1, Version v2)
  374. {
  375. return (v2 < v1);
  376. }
  377. public static bool operator >=(Version v1, Version v2)
  378. {
  379. return (v2 <= v1);
  380. }
  381. }
  382. }