Fix IDE0025 (use expression body for properties)
[mono-project.git] / netcore / System.Private.CoreLib / shared / System / Version.cs
blob1778f64b17550eb9af42f992a352e46c65525540
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;
7 using System.Text;
8 using System.Runtime.CompilerServices;
9 using System.Diagnostics.CodeAnalysis;
11 namespace System
13 // A Version object contains four hierarchical numeric components: major, minor,
14 // build and revision. Build and revision may be unspecified, which is represented
15 // internally as a -1. By definition, an unspecified component matches anything
16 // (both unspecified and specified), and an unspecified component is "less than" any
17 // specified component.
19 [Serializable]
20 [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
21 public sealed class Version : ICloneable, IComparable, IComparable<Version?>,
22 #nullable disable // see comment on String
23 IEquatable<Version>,
24 #nullable restore
25 ISpanFormattable
27 // AssemblyName depends on the order staying the same
28 private readonly int _Major; // Do not rename (binary serialization)
29 private readonly int _Minor; // Do not rename (binary serialization)
30 private readonly int _Build = -1; // Do not rename (binary serialization)
31 private readonly int _Revision = -1; // Do not rename (binary serialization)
33 public Version(int major, int minor, int build, int revision)
35 if (major < 0)
36 throw new ArgumentOutOfRangeException(nameof(major), SR.ArgumentOutOfRange_Version);
38 if (minor < 0)
39 throw new ArgumentOutOfRangeException(nameof(minor), SR.ArgumentOutOfRange_Version);
41 if (build < 0)
42 throw new ArgumentOutOfRangeException(nameof(build), SR.ArgumentOutOfRange_Version);
44 if (revision < 0)
45 throw new ArgumentOutOfRangeException(nameof(revision), SR.ArgumentOutOfRange_Version);
47 _Major = major;
48 _Minor = minor;
49 _Build = build;
50 _Revision = revision;
53 public Version(int major, int minor, int build)
55 if (major < 0)
56 throw new ArgumentOutOfRangeException(nameof(major), SR.ArgumentOutOfRange_Version);
58 if (minor < 0)
59 throw new ArgumentOutOfRangeException(nameof(minor), SR.ArgumentOutOfRange_Version);
61 if (build < 0)
62 throw new ArgumentOutOfRangeException(nameof(build), SR.ArgumentOutOfRange_Version);
65 _Major = major;
66 _Minor = minor;
67 _Build = build;
70 public Version(int major, int minor)
72 if (major < 0)
73 throw new ArgumentOutOfRangeException(nameof(major), SR.ArgumentOutOfRange_Version);
75 if (minor < 0)
76 throw new ArgumentOutOfRangeException(nameof(minor), SR.ArgumentOutOfRange_Version);
78 _Major = major;
79 _Minor = minor;
82 public Version(string version)
84 Version v = Version.Parse(version);
85 _Major = v.Major;
86 _Minor = v.Minor;
87 _Build = v.Build;
88 _Revision = v.Revision;
91 public Version()
93 _Major = 0;
94 _Minor = 0;
97 private Version(Version version)
99 Debug.Assert(version != null);
101 _Major = version._Major;
102 _Minor = version._Minor;
103 _Build = version._Build;
104 _Revision = version._Revision;
107 public object Clone()
109 return new Version(this);
112 // Properties for setting and getting version numbers
113 public int Major => _Major;
115 public int Minor => _Minor;
117 public int Build => _Build;
119 public int Revision => _Revision;
121 public short MajorRevision => (short)(_Revision >> 16);
123 public short MinorRevision => (short)(_Revision & 0xFFFF);
125 public int CompareTo(object? version)
127 if (version == null)
129 return 1;
132 if (version is Version v)
134 return CompareTo(v);
137 throw new ArgumentException(SR.Arg_MustBeVersion);
140 public int CompareTo(Version? value)
142 return
143 object.ReferenceEquals(value, this) ? 0 :
144 value is null ? 1 :
145 _Major != value._Major ? (_Major > value._Major ? 1 : -1) :
146 _Minor != value._Minor ? (_Minor > value._Minor ? 1 : -1) :
147 _Build != value._Build ? (_Build > value._Build ? 1 : -1) :
148 _Revision != value._Revision ? (_Revision > value._Revision ? 1 : -1) :
152 public override bool Equals(object? obj)
154 return Equals(obj as Version);
157 public bool Equals(Version? obj)
159 return object.ReferenceEquals(obj, this) ||
160 (!(obj is null) &&
161 _Major == obj._Major &&
162 _Minor == obj._Minor &&
163 _Build == obj._Build &&
164 _Revision == obj._Revision);
167 public override int GetHashCode()
169 // Let's assume that most version numbers will be pretty small and just
170 // OR some lower order bits together.
172 int accumulator = 0;
174 accumulator |= (_Major & 0x0000000F) << 28;
175 accumulator |= (_Minor & 0x000000FF) << 20;
176 accumulator |= (_Build & 0x000000FF) << 12;
177 accumulator |= (_Revision & 0x00000FFF);
179 return accumulator;
182 public override string ToString() =>
183 ToString(DefaultFormatFieldCount);
185 public string ToString(int fieldCount) =>
186 fieldCount == 0 ? string.Empty :
187 fieldCount == 1 ? _Major.ToString() :
188 StringBuilderCache.GetStringAndRelease(ToCachedStringBuilder(fieldCount));
190 public bool TryFormat(Span<char> destination, out int charsWritten) =>
191 TryFormat(destination, DefaultFormatFieldCount, out charsWritten);
193 public bool TryFormat(Span<char> destination, int fieldCount, out int charsWritten)
195 if (fieldCount == 0)
197 charsWritten = 0;
198 return true;
200 else if (fieldCount == 1)
202 return _Major.TryFormat(destination, out charsWritten);
205 StringBuilder sb = ToCachedStringBuilder(fieldCount);
206 if (sb.Length <= destination.Length)
208 sb.CopyTo(0, destination, sb.Length);
209 StringBuilderCache.Release(sb);
210 charsWritten = sb.Length;
211 return true;
214 StringBuilderCache.Release(sb);
215 charsWritten = 0;
216 return false;
219 bool ISpanFormattable.TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider)
221 // format and provider are ignored.
222 return TryFormat(destination, out charsWritten);
225 private int DefaultFormatFieldCount =>
226 _Build == -1 ? 2 :
227 _Revision == -1 ? 3 :
230 private StringBuilder ToCachedStringBuilder(int fieldCount)
232 // Note: As we always have positive numbers then it is safe to convert the number to string
233 // regardless of the current culture as we'll not have any punctuation marks in the number.
235 if (fieldCount == 2)
237 StringBuilder sb = StringBuilderCache.Acquire();
238 sb.Append(_Major);
239 sb.Append('.');
240 sb.Append(_Minor);
241 return sb;
243 else
245 if (_Build == -1)
247 throw new ArgumentException(SR.Format(SR.ArgumentOutOfRange_Bounds_Lower_Upper, "0", "2"), nameof(fieldCount));
250 if (fieldCount == 3)
252 StringBuilder sb = StringBuilderCache.Acquire();
253 sb.Append(_Major);
254 sb.Append('.');
255 sb.Append(_Minor);
256 sb.Append('.');
257 sb.Append(_Build);
258 return sb;
261 if (_Revision == -1)
263 throw new ArgumentException(SR.Format(SR.ArgumentOutOfRange_Bounds_Lower_Upper, "0", "3"), nameof(fieldCount));
266 if (fieldCount == 4)
268 StringBuilder sb = StringBuilderCache.Acquire();
269 sb.Append(_Major);
270 sb.Append('.');
271 sb.Append(_Minor);
272 sb.Append('.');
273 sb.Append(_Build);
274 sb.Append('.');
275 sb.Append(_Revision);
276 return sb;
279 throw new ArgumentException(SR.Format(SR.ArgumentOutOfRange_Bounds_Lower_Upper, "0", "4"), nameof(fieldCount));
283 public static Version Parse(string input)
285 if (input == null)
287 throw new ArgumentNullException(nameof(input));
290 return ParseVersion(input.AsSpan(), throwOnFailure: true)!;
293 public static Version Parse(ReadOnlySpan<char> input) =>
294 ParseVersion(input, throwOnFailure: true)!;
296 public static bool TryParse(string? input, [NotNullWhen(true)] out Version? result)
298 if (input == null)
300 result = null;
301 return false;
304 return (result = ParseVersion(input.AsSpan(), throwOnFailure: false)) != null;
307 public static bool TryParse(ReadOnlySpan<char> input, [NotNullWhen(true)] out Version? result) =>
308 (result = ParseVersion(input, throwOnFailure: false)) != null;
310 private static Version? ParseVersion(ReadOnlySpan<char> input, bool throwOnFailure)
312 // Find the separator between major and minor. It must exist.
313 int majorEnd = input.IndexOf('.');
314 if (majorEnd < 0)
316 if (throwOnFailure) throw new ArgumentException(SR.Arg_VersionString, nameof(input));
317 return null;
320 // Find the ends of the optional minor and build portions.
321 // We musn't have any separators after build.
322 int buildEnd = -1;
323 int minorEnd = input.Slice(majorEnd + 1).IndexOf('.');
324 if (minorEnd != -1)
326 minorEnd += (majorEnd + 1);
327 buildEnd = input.Slice(minorEnd + 1).IndexOf('.');
328 if (buildEnd != -1)
330 buildEnd += (minorEnd + 1);
331 if (input.Slice(buildEnd + 1).Contains('.'))
333 if (throwOnFailure) throw new ArgumentException(SR.Arg_VersionString, nameof(input));
334 return null;
339 int minor, build, revision;
341 // Parse the major version
342 if (!TryParseComponent(input.Slice(0, majorEnd), nameof(input), throwOnFailure, out int major))
344 return null;
347 if (minorEnd != -1)
349 // If there's more than a major and minor, parse the minor, too.
350 if (!TryParseComponent(input.Slice(majorEnd + 1, minorEnd - majorEnd - 1), nameof(input), throwOnFailure, out minor))
352 return null;
355 if (buildEnd != -1)
357 // major.minor.build.revision
358 return
359 TryParseComponent(input.Slice(minorEnd + 1, buildEnd - minorEnd - 1), nameof(build), throwOnFailure, out build) &&
360 TryParseComponent(input.Slice(buildEnd + 1), nameof(revision), throwOnFailure, out revision) ?
361 new Version(major, minor, build, revision) :
362 null;
364 else
366 // major.minor.build
367 return TryParseComponent(input.Slice(minorEnd + 1), nameof(build), throwOnFailure, out build) ?
368 new Version(major, minor, build) :
369 null;
372 else
374 // major.minor
375 return TryParseComponent(input.Slice(majorEnd + 1), nameof(input), throwOnFailure, out minor) ?
376 new Version(major, minor) :
377 null;
381 private static bool TryParseComponent(ReadOnlySpan<char> component, string componentName, bool throwOnFailure, out int parsedComponent)
383 if (throwOnFailure)
385 if ((parsedComponent = int.Parse(component, NumberStyles.Integer, CultureInfo.InvariantCulture)) < 0)
387 throw new ArgumentOutOfRangeException(componentName, SR.ArgumentOutOfRange_Version);
389 return true;
392 return int.TryParse(component, NumberStyles.Integer, CultureInfo.InvariantCulture, out parsedComponent) && parsedComponent >= 0;
395 // Force inline as the true/false ternary takes it above ALWAYS_INLINE size even though the asm ends up smaller
396 [MethodImpl(MethodImplOptions.AggressiveInlining)]
397 public static bool operator ==(Version? v1, Version? v2)
399 // Test "right" first to allow branch elimination when inlined for null checks (== null)
400 // so it can become a simple test
401 if (v2 is null)
403 // return true/false not the test result https://github.com/dotnet/coreclr/issues/914
404 return (v1 is null) ? true : false;
407 // Quick reference equality test prior to calling the virtual Equality
408 return ReferenceEquals(v2, v1) ? true : v2.Equals(v1);
411 public static bool operator !=(Version? v1, Version? v2)
413 return !(v1 == v2);
416 public static bool operator <(Version? v1, Version? v2)
418 if (v1 is null)
420 return !(v2 is null);
423 return (v1.CompareTo(v2) < 0);
426 public static bool operator <=(Version? v1, Version? v2)
428 if (v1 is null)
430 return true;
433 return (v1.CompareTo(v2) <= 0);
436 public static bool operator >(Version? v1, Version? v2)
438 return (v2 < v1);
441 public static bool operator >=(Version? v1, Version? v2)
443 return (v2 <= v1);