using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.CompilerServices;
[Newtonsoft.Json.JsonConverterAttribute(typeof(Newtonsoft.Json.Converters.VersionConverter))]
public Version V {get; set;} = new Version(1,0);
static void Main(string[] args)
var json = "{\"V\":{\"Major\":2,\"Minor\":0,\"Build\":-1,\"Revision\":-1,\"MajorRevision\":-1,\"MinorRevision\":-1}}";
Console.WriteLine($".NET: {System.Environment.Version}");
Console.WriteLine($"Json.NET: {System.Reflection.Assembly.GetAssembly(typeof(Newtonsoft.Json.JsonConvert))}");
var b = Newtonsoft.Json.JsonConvert.DeserializeObject<Versioned>(json);
Console.WriteLine(b.V.Major);
catch (Exception ex) { Console.WriteLine(ex.GetBaseException().Message); }
public sealed class MyVersion : ICloneable, IComparable, IComparable<MyVersion?>, IEquatable<MyVersion?>
private readonly int _Major;
private readonly int _Minor;
private readonly int _Build;
private readonly int _Revision;
public MyVersion(int major, int minor, int build, int revision)
throw new ArgumentOutOfRangeException(nameof(major), "OOR");
throw new ArgumentOutOfRangeException(nameof(minor), "OOR");
throw new ArgumentOutOfRangeException(nameof(build), "OOR");
throw new ArgumentOutOfRangeException(nameof(revision), "OOR");
public MyVersion(int major, int minor, int build)
throw new ArgumentOutOfRangeException(nameof(major), "OOR");
throw new ArgumentOutOfRangeException(nameof(minor), "OOR");
throw new ArgumentOutOfRangeException(nameof(build), "OOR");
public MyVersion(int major, int minor)
throw new ArgumentOutOfRangeException(nameof(major), "OOR");
throw new ArgumentOutOfRangeException(nameof(minor), "OOR");
public MyVersion(string version)
Version v = Version.Parse(version);
private MyVersion(MyVersion version)
Debug.Assert(version != null);
_Revision = version._Revision;
return new MyVersion(this);
public int Major => _Major;
public int Minor => _Minor;
public int Build => _Build;
public int Revision => _Revision;
public short MajorRevision => (short)(_Revision >> 16);
public short MinorRevision => (short)(_Revision & 0xFFFF);
public int CompareTo(object? version)
if (version is Version v)
throw new ArgumentException("BOOM");
public int CompareTo(MyVersion? value)
object.ReferenceEquals(value, this) ? 0 :
_Major != value._Major ? (_Major > value._Major ? 1 : -1) :
_Minor != value._Minor ? (_Minor > value._Minor ? 1 : -1) :
_Build != value._Build ? (_Build > value._Build ? 1 : -1) :
_Revision != value._Revision ? (_Revision > value._Revision ? 1 : -1) :
public override bool Equals(object? obj)
return Equals(obj as MyVersion);
public bool Equals(MyVersion? obj)
return object.ReferenceEquals(obj, this) ||
_Revision == obj._Revision);
public override int GetHashCode()
accumulator |= (_Major & 0x0000000F) << 28;
accumulator |= (_Minor & 0x000000FF) << 20;
accumulator |= (_Build & 0x000000FF) << 12;
accumulator |= (_Revision & 0x00000FFF);
public static Version Parse(string input)
throw new ArgumentNullException(nameof(input));
return ParseVersion(input.AsSpan(), throwOnFailure: true)!;
public static Version Parse(ReadOnlySpan<char> input) =>
ParseVersion(input, throwOnFailure: true)!;
public static bool TryParse([NotNullWhen(true)] string? input, [NotNullWhen(true)] out Version? result)
return (result = ParseVersion(input.AsSpan(), throwOnFailure: false)) != null;
public static bool TryParse(ReadOnlySpan<char> input, [NotNullWhen(true)] out Version? result) =>
(result = ParseVersion(input, throwOnFailure: false)) != null;
private static Version? ParseVersion(ReadOnlySpan<char> input, bool throwOnFailure)
int majorEnd = input.IndexOf('.');
if (throwOnFailure) throw new ArgumentException("BOOM", nameof(input));
int minorEnd = input.Slice(majorEnd + 1).IndexOf('.');
minorEnd += (majorEnd + 1);
buildEnd = input.Slice(minorEnd + 1).IndexOf('.');
buildEnd += (minorEnd + 1);
if (input.Slice(buildEnd + 1).Contains('.'))
if (throwOnFailure) throw new ArgumentException("BOOM", nameof(input));
int minor, build, revision;
if (!TryParseComponent(input.Slice(0, majorEnd), nameof(input), throwOnFailure, out int major))
if (!TryParseComponent(input.Slice(majorEnd + 1, minorEnd - majorEnd - 1), nameof(input), throwOnFailure, out minor))
TryParseComponent(input.Slice(minorEnd + 1, buildEnd - minorEnd - 1), nameof(build), throwOnFailure, out build) &&
TryParseComponent(input.Slice(buildEnd + 1), nameof(revision), throwOnFailure, out revision) ?
new Version(major, minor, build, revision) :
return TryParseComponent(input.Slice(minorEnd + 1), nameof(build), throwOnFailure, out build) ?
new Version(major, minor, build) :
return TryParseComponent(input.Slice(majorEnd + 1), nameof(input), throwOnFailure, out minor) ?
new Version(major, minor) :
private static bool TryParseComponent(ReadOnlySpan<char> component, string componentName, bool throwOnFailure, out int parsedComponent)
if ((parsedComponent = int.Parse(component, NumberStyles.Integer, CultureInfo.InvariantCulture)) < 0)
throw new ArgumentOutOfRangeException(componentName, "BOOM");
return int.TryParse(component, NumberStyles.Integer, CultureInfo.InvariantCulture, out parsedComponent) && parsedComponent >= 0;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(MyVersion? v1, MyVersion? v2)
return (v1 is null) ? true : false;
return ReferenceEquals(v2, v1) ? true : v2.Equals(v1);
public static bool operator !=(MyVersion? v1, MyVersion? v2) => !(v1 == v2);
public static bool operator <(MyVersion? v1, MyVersion? v2)
return v1.CompareTo(v2) < 0;
public static bool operator <=(MyVersion? v1, MyVersion? v2)
return v1.CompareTo(v2) <= 0;
public static bool operator >(MyVersion? v1, MyVersion? v2) => v2 < v1;
public static bool operator >=(MyVersion? v1, MyVersion? v2) => v2 <= v1;