You can use BinaryPrimitives.ReadUInt64BigEndian
instead if you are on .NET Core 2.1 or later (or .NET Standard).
Last active
October 20, 2023 14:02
-
-
Save jnm2/929d194c87df8ad0438f6cab0139a0a6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Diagnostics; | |
using System.Numerics; | |
#if NETCOREAPP2_1_OR_GREATER | |
using System.Buffers.Binary; | |
#endif | |
#nullable enable | |
[DebuggerDisplay("{ToString(),nq}")] | |
public readonly struct Rowversion : IComparable, IEquatable<Rowversion>, IComparable<Rowversion> | |
#if NET7_0_OR_GREATER | |
, IComparisonOperators<Rowversion, Rowversion, bool> | |
#endif | |
{ | |
public static Rowversion Zero => default; | |
private readonly ulong value; | |
public Rowversion(ulong value) | |
{ | |
this.value = value; | |
} | |
#if !NETCOREAPP2_1_OR_GREATER | |
public Rowversion(byte[] value) | |
{ | |
if (value is null) | |
throw new ArgumentNullException(nameof(value)); | |
if (value.Length != 8) | |
throw new ArgumentException("The array does not have the correct length (8 bytes) to represent a SQL Server rowversion.", nameof(value)); | |
this.value = | |
((ulong)value[0] << 56) | |
| ((ulong)value[1] << 48) | |
| ((ulong)value[2] << 40) | |
| ((ulong)value[3] << 32) | |
| ((ulong)value[4] << 24) | |
| ((ulong)value[5] << 16) | |
| ((ulong)value[6] << 8) | |
| value[7]; | |
} | |
#else | |
public Rowversion(ReadOnlySpan<byte> value) | |
{ | |
this.value = BinaryPrimitives.ReadUInt64BigEndian(value); | |
} | |
#endif | |
public static implicit operator Rowversion(ulong value) => new(value); | |
public static implicit operator Rowversion(long value) => new(unchecked((ulong)value)); | |
public static explicit operator Rowversion(byte[] value) => new(value); | |
public static explicit operator Rowversion?(byte[]? value) => value is null ? null : new Rowversion(value); | |
public byte[] ToArray() | |
{ | |
var array = new byte[8]; | |
#if NETCOREAPP2_1_OR_GREATER | |
WriteTo(array); | |
#else | |
array[0] = (byte)(value >> 56); | |
array[1] = (byte)(value >> 48); | |
array[2] = (byte)(value >> 40); | |
array[3] = (byte)(value >> 32); | |
array[4] = (byte)(value >> 24); | |
array[5] = (byte)(value >> 16); | |
array[6] = (byte)(value >> 8); | |
array[7] = (byte)value; | |
#endif | |
return array; | |
} | |
#if NETCOREAPP2_1_OR_GREATER | |
public void WriteTo(Span<byte> destination) | |
{ | |
BinaryPrimitives.WriteUInt64BigEndian(destination, value); | |
} | |
#endif | |
public override bool Equals(object? obj) => obj is Rowversion other && Equals(other); | |
public override int GetHashCode() => value.GetHashCode(); | |
public bool Equals(Rowversion other) => other.value == value; | |
int IComparable.CompareTo(object? obj) => obj == null ? 1 : CompareTo((Rowversion)obj); | |
public int CompareTo(Rowversion other) => value.CompareTo(other.value); | |
public static bool operator ==(Rowversion left, Rowversion right) => left.Equals(right); | |
public static bool operator !=(Rowversion left, Rowversion right) => !left.Equals(right); | |
public static bool operator >(Rowversion left, Rowversion right) => left.CompareTo(right) > 0; | |
public static bool operator >=(Rowversion left, Rowversion right) => left.CompareTo(right) >= 0; | |
public static bool operator <(Rowversion left, Rowversion right) => left.CompareTo(right) < 0; | |
public static bool operator <=(Rowversion left, Rowversion right) => left.CompareTo(right) <= 0; | |
public override string ToString() => value.ToString("x16"); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@daiplusplus Hi!
Span<byte>
didn't exist when this was written, butReadOnlySpan<byte>
for the constructor would be an obvious improvement.I'd never heard that implicit compile-time conversions should affect runtime conversions via
Equals(object)
. I'm pretty sure that would be nonstandard and possibly even not best practice. See https://learn.microsoft.com/en-us/dotnet/api/system.object.equals#notes-for-inheritors. For instance, the requirement that:This requirement can't possibly be satisfied unless equality checks require the exact concrete type to match. You can also see the importance borne out in the attention that the C# 9 records feature paid to this concern, which is why there's an EqualityContract property on every record so that equality works properly with inheritance.
I don't think so. This is where you'd pass a ulong that you queried from SQL Server as such, and that scenario would break if it was changed from what it is now.
The numeric value of the ulong should not be interpreted as having a byte order at all for the purpose of this struct. It's the same numeric value as the numeric value SQL Server would see. In other words, don't use the ulong constructor to secretly pass a byte sequence rather than a numeric value. Use the byte constructor for that.
A better ArgumentException for length would be a good idea.
Now that nullable reference types exist, that sounds like a good idea.
That sounds cool. I haven't run into code in the wild where I could imagine the motivation for interop via the generic math interfaces, since IComparable already covers the comparison operators.