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.
5 using System.Globalization;
6 using System.Diagnostics;
11 // A Version object contains four hierarchical numeric components: major, minor,
12 // build and revision. Build and revision may be unspecified, which is represented
13 // internally as a -1. By definition, an unspecified component matches anything
14 // (both unspecified and specified), and an unspecified component is "less than" any
15 // specified component.
18 [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
19 public sealed class Version : ICloneable, IComparable, IComparable<Version>, IEquatable<Version>, ISpanFormattable
21 // AssemblyName depends on the order staying the same
22 private readonly int _Major; // Do not rename (binary serialization)
23 private readonly int _Minor; // Do not rename (binary serialization)
24 private readonly int _Build = -1; // Do not rename (binary serialization)
25 private readonly int _Revision = -1; // Do not rename (binary serialization)
27 public Version(int major, int minor, int build, int revision)
30 throw new ArgumentOutOfRangeException(nameof(major), SR.ArgumentOutOfRange_Version);
33 throw new ArgumentOutOfRangeException(nameof(minor), SR.ArgumentOutOfRange_Version);
36 throw new ArgumentOutOfRangeException(nameof(build), SR.ArgumentOutOfRange_Version);
39 throw new ArgumentOutOfRangeException(nameof(revision), SR.ArgumentOutOfRange_Version);
47 public Version(int major, int minor, int build)
50 throw new ArgumentOutOfRangeException(nameof(major), SR.ArgumentOutOfRange_Version);
53 throw new ArgumentOutOfRangeException(nameof(minor), SR.ArgumentOutOfRange_Version);
56 throw new ArgumentOutOfRangeException(nameof(build), SR.ArgumentOutOfRange_Version);
64 public Version(int major, int minor)
67 throw new ArgumentOutOfRangeException(nameof(major), SR.ArgumentOutOfRange_Version);
70 throw new ArgumentOutOfRangeException(nameof(minor), SR.ArgumentOutOfRange_Version);
76 public Version(String version)
78 Version v = Version.Parse(version);
82 _Revision = v.Revision;
91 private Version(Version version)
93 Debug.Assert(version != null);
95 _Major = version._Major;
96 _Minor = version._Minor;
97 _Build = version._Build;
98 _Revision = version._Revision;
101 public object Clone()
103 return new Version(this);
106 // Properties for setting and getting version numbers
109 get { return _Major; }
114 get { return _Minor; }
119 get { return _Build; }
124 get { return _Revision; }
127 public short MajorRevision
129 get { return (short)(_Revision >> 16); }
132 public short MinorRevision
134 get { return (short)(_Revision & 0xFFFF); }
137 public int CompareTo(Object version)
144 Version v = version as Version;
147 throw new ArgumentException(SR.Arg_MustBeVersion);
153 public int CompareTo(Version value)
156 object.ReferenceEquals(value, this) ? 0 :
157 object.ReferenceEquals(value, null) ? 1 :
158 _Major != value._Major ? (_Major > value._Major ? 1 : -1) :
159 _Minor != value._Minor ? (_Minor > value._Minor ? 1 : -1) :
160 _Build != value._Build ? (_Build > value._Build ? 1 : -1) :
161 _Revision != value._Revision ? (_Revision > value._Revision ? 1 : -1) :
165 public override bool Equals(Object obj)
167 return Equals(obj as Version);
170 public bool Equals(Version obj)
172 return object.ReferenceEquals(obj, this) ||
173 (!object.ReferenceEquals(obj, null) &&
174 _Major == obj._Major &&
175 _Minor == obj._Minor &&
176 _Build == obj._Build &&
177 _Revision == obj._Revision);
180 public override int GetHashCode()
182 // Let's assume that most version numbers will be pretty small and just
183 // OR some lower order bits together.
187 accumulator |= (_Major & 0x0000000F) << 28;
188 accumulator |= (_Minor & 0x000000FF) << 20;
189 accumulator |= (_Build & 0x000000FF) << 12;
190 accumulator |= (_Revision & 0x00000FFF);
195 public override string ToString() =>
196 ToString(DefaultFormatFieldCount);
198 public string ToString(int fieldCount) =>
199 fieldCount == 0 ? string.Empty :
200 fieldCount == 1 ? _Major.ToString() :
201 StringBuilderCache.GetStringAndRelease(ToCachedStringBuilder(fieldCount));
203 public bool TryFormat(Span<char> destination, out int charsWritten) =>
204 TryFormat(destination, DefaultFormatFieldCount, out charsWritten);
206 public bool TryFormat(Span<char> destination, int fieldCount, out int charsWritten)
213 else if (fieldCount == 1)
215 return _Major.TryFormat(destination, out charsWritten);
218 StringBuilder sb = ToCachedStringBuilder(fieldCount);
219 if (sb.Length <= destination.Length)
221 sb.CopyTo(0, destination, sb.Length);
222 StringBuilderCache.Release(sb);
223 charsWritten = sb.Length;
227 StringBuilderCache.Release(sb);
232 bool ISpanFormattable.TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider provider)
234 // format and provider are ignored.
235 return TryFormat(destination, out charsWritten);
238 private int DefaultFormatFieldCount =>
240 _Revision == -1 ? 3 :
243 private StringBuilder ToCachedStringBuilder(int fieldCount)
245 // Note: As we always have positive numbers then it is safe to convert the number to string
246 // regardless of the current culture as we'll not have any punctuation marks in the number.
250 StringBuilder sb = StringBuilderCache.Acquire();
260 throw new ArgumentException(SR.Format(SR.ArgumentOutOfRange_Bounds_Lower_Upper, "0", "2"), nameof(fieldCount));
265 StringBuilder sb = StringBuilderCache.Acquire();
276 throw new ArgumentException(SR.Format(SR.ArgumentOutOfRange_Bounds_Lower_Upper, "0", "3"), nameof(fieldCount));
281 StringBuilder sb = StringBuilderCache.Acquire();
288 sb.Append(_Revision);
292 throw new ArgumentException(SR.Format(SR.ArgumentOutOfRange_Bounds_Lower_Upper, "0", "4"), nameof(fieldCount));
296 public static Version Parse(string input)
300 throw new ArgumentNullException(nameof(input));
303 return ParseVersion(input.AsSpan(), throwOnFailure: true);
306 public static Version Parse(ReadOnlySpan<char> input) =>
307 ParseVersion(input, throwOnFailure: true);
309 public static bool TryParse(string input, out Version result)
317 return (result = ParseVersion(input.AsSpan(), throwOnFailure: false)) != null;
320 public static bool TryParse(ReadOnlySpan<char> input, out Version result) =>
321 (result = ParseVersion(input, throwOnFailure: false)) != null;
323 private static Version ParseVersion(ReadOnlySpan<char> input, bool throwOnFailure)
325 // Find the separator between major and minor. It must exist.
326 int majorEnd = input.IndexOf('.');
329 if (throwOnFailure) throw new ArgumentException(SR.Arg_VersionString, nameof(input));
333 // Find the ends of the optional minor and build portions.
334 // We musn't have any separators after build.
336 int minorEnd = input.IndexOf('.', majorEnd + 1);
339 buildEnd = input.IndexOf('.', minorEnd + 1);
342 if (input.IndexOf('.', buildEnd + 1) != -1)
344 if (throwOnFailure) throw new ArgumentException(SR.Arg_VersionString, nameof(input));
350 int major, minor, build, revision;
352 // Parse the major version
353 if (!TryParseComponent(input.Slice(0, majorEnd), nameof(input), throwOnFailure, out major))
360 // If there's more than a major and minor, parse the minor, too.
361 if (!TryParseComponent(input.Slice(majorEnd + 1, minorEnd - majorEnd - 1), nameof(input), throwOnFailure, out minor))
368 // major.minor.build.revision
370 TryParseComponent(input.Slice(minorEnd + 1, buildEnd - minorEnd - 1), nameof(build), throwOnFailure, out build) &&
371 TryParseComponent(input.Slice(buildEnd + 1), nameof(revision), throwOnFailure, out revision) ?
372 new Version(major, minor, build, revision) :
378 return TryParseComponent(input.Slice(minorEnd + 1), nameof(build), throwOnFailure, out build) ?
379 new Version(major, minor, build) :
386 return TryParseComponent(input.Slice(majorEnd + 1), nameof(input), throwOnFailure, out minor) ?
387 new Version(major, minor) :
392 private static bool TryParseComponent(ReadOnlySpan<char> component, string componentName, bool throwOnFailure, out int parsedComponent)
396 if ((parsedComponent = int.Parse(component, NumberStyles.Integer, CultureInfo.InvariantCulture)) < 0)
398 throw new ArgumentOutOfRangeException(componentName, SR.ArgumentOutOfRange_Version);
403 return int.TryParse(component, NumberStyles.Integer, CultureInfo.InvariantCulture, out parsedComponent) && parsedComponent >= 0;
406 public static bool operator ==(Version v1, Version v2)
408 if (Object.ReferenceEquals(v1, null))
410 return Object.ReferenceEquals(v2, null);
413 return v1.Equals(v2);
416 public static bool operator !=(Version v1, Version v2)
421 public static bool operator <(Version v1, Version v2)
423 if ((Object)v1 == null)
424 throw new ArgumentNullException(nameof(v1));
425 return (v1.CompareTo(v2) < 0);
428 public static bool operator <=(Version v1, Version v2)
430 if ((Object)v1 == null)
431 throw new ArgumentNullException(nameof(v1));
432 return (v1.CompareTo(v2) <= 0);
435 public static bool operator >(Version v1, Version v2)
440 public static bool operator >=(Version v1, Version v2)