// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; namespace CommunityToolkit.Diagnostics; /// /// Helper methods to verify conditions when running code. /// public static partial class Guard { /// /// Asserts that the input value is . /// /// The type of value type being tested. /// The input value to test. /// The name of the input parameter being tested. /// Thrown if is not . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void IsDefault(T value, [CallerArgumentExpression("value")] string name = "") where T : struct, IEquatable { if (value.Equals(default)) { return; } ThrowHelper.ThrowArgumentExceptionForIsDefault(value, name); } /// /// Asserts that the input value is not . /// /// The type of value type being tested. /// The input value to test. /// The name of the input parameter being tested. /// Thrown if is . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void IsNotDefault(T value, [CallerArgumentExpression("value")] string name = "") where T : struct, IEquatable { if (!value.Equals(default)) { return; } ThrowHelper.ThrowArgumentExceptionForIsNotDefault(name); } /// /// Asserts that the input value must be equal to a specified value. /// /// The type of input values to compare. /// The input value to test. /// The target value to test for. /// The name of the input parameter being tested. /// Thrown if is != . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void IsEqualTo(T value, T target, [CallerArgumentExpression("value")] string name = "") where T : notnull, IEquatable { if (value.Equals(target)) { return; } ThrowHelper.ThrowArgumentExceptionForIsEqualTo(value, target, name); } /// /// Asserts that the input value must be not equal to a specified value. /// /// The type of input values to compare. /// The input value to test. /// The target value to test for. /// The name of the input parameter being tested. /// Thrown if is == . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void IsNotEqualTo(T value, T target, [CallerArgumentExpression("value")] string name = "") where T : notnull, IEquatable { if (!value.Equals(target)) { return; } ThrowHelper.ThrowArgumentExceptionForIsNotEqualTo(value, target, name); } /// /// Asserts that the input value must be a bitwise match with a specified value. /// /// The type of input values to compare. /// The input value to test. /// The target value to test for. /// The name of the input parameter being tested. /// Thrown if is not a bitwise match for . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void IsBitwiseEqualTo(T value, T target, [CallerArgumentExpression("value")] string name = "") where T : unmanaged { // Include some fast paths if the input type is of size 1, 2, 4, 8, or 16. // In those cases, just reinterpret the bytes as values of an integer type, // and compare them directly, which is much faster than having a loop over each byte. // The conditional branches below are known at compile time by the JIT compiler, // so that only the right one will actually be translated into native code. if (sizeof(T) == 1) { byte valueByte = Unsafe.As(ref value); byte targetByte = Unsafe.As(ref target); if (valueByte == targetByte) { return; } ThrowHelper.ThrowArgumentExceptionForBitwiseEqualTo(value, target, name); } else if (sizeof(T) == 2) { ushort valueUShort = Unsafe.As(ref value); ushort targetUShort = Unsafe.As(ref target); if (valueUShort == targetUShort) { return; } ThrowHelper.ThrowArgumentExceptionForBitwiseEqualTo(value, target, name); } else if (sizeof(T) == 4) { uint valueUInt = Unsafe.As(ref value); uint targetUInt = Unsafe.As(ref target); if (valueUInt == targetUInt) { return; } ThrowHelper.ThrowArgumentExceptionForBitwiseEqualTo(value, target, name); } else if (sizeof(T) == 8) { ulong valueULong = Unsafe.As(ref value); ulong targetULong = Unsafe.As(ref target); if (Bit64Compare(ref valueULong, ref targetULong)) { return; } ThrowHelper.ThrowArgumentExceptionForBitwiseEqualTo(value, target, name); } else if (sizeof(T) == 16) { ulong valueULong0 = Unsafe.As(ref value); ulong targetULong0 = Unsafe.As(ref target); if (Bit64Compare(ref valueULong0, ref targetULong0)) { ulong valueULong1 = Unsafe.Add(ref Unsafe.As(ref value), 1); ulong targetULong1 = Unsafe.Add(ref Unsafe.As(ref target), 1); if (Bit64Compare(ref valueULong1, ref targetULong1)) { return; } } ThrowHelper.ThrowArgumentExceptionForBitwiseEqualTo(value, target, name); } else { Span valueBytes = new(Unsafe.AsPointer(ref value), sizeof(T)); Span targetBytes = new(Unsafe.AsPointer(ref target), sizeof(T)); if (valueBytes.SequenceEqual(targetBytes)) { return; } ThrowHelper.ThrowArgumentExceptionForBitwiseEqualTo(value, target, name); } } // Compares 64 bits of data from two given memory locations for bitwise equality [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] private static unsafe bool Bit64Compare(ref ulong left, ref ulong right) { // Handles 32 bit case, because using ulong is inefficient if (sizeof(IntPtr) == 4) { ref int r0 = ref Unsafe.As(ref left); ref int r1 = ref Unsafe.As(ref right); return r0 == r1 && Unsafe.Add(ref r0, 1) == Unsafe.Add(ref r1, 1); } return left == right; } /// /// Asserts that the input value must be less than a specified value. /// /// The type of input values to compare. /// The input value to test. /// The exclusive maximum value that is accepted. /// The name of the input parameter being tested. /// Thrown if is >= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void IsLessThan(T value, T maximum, [CallerArgumentExpression("value")] string name = "") where T : notnull, IComparable { if (value.CompareTo(maximum) < 0) { return; } ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsLessThan(value, maximum, name); } /// /// Asserts that the input value must be less than or equal to a specified value. /// /// The type of input values to compare. /// The input value to test. /// The inclusive maximum value that is accepted. /// The name of the input parameter being tested. /// Thrown if is > . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void IsLessThanOrEqualTo(T value, T maximum, [CallerArgumentExpression("value")] string name = "") where T : notnull, IComparable { if (value.CompareTo(maximum) <= 0) { return; } ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsLessThanOrEqualTo(value, maximum, name); } /// /// Asserts that the input value must be greater than a specified value. /// /// The type of input values to compare. /// The input value to test. /// The exclusive minimum value that is accepted. /// The name of the input parameter being tested. /// Thrown if is <= . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void IsGreaterThan(T value, T minimum, [CallerArgumentExpression("value")] string name = "") where T : notnull, IComparable { if (value.CompareTo(minimum) > 0) { return; } ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsGreaterThan(value, minimum, name); } /// /// Asserts that the input value must be greater than or equal to a specified value. /// /// The type of input values to compare. /// The input value to test. /// The inclusive minimum value that is accepted. /// The name of the input parameter being tested. /// Thrown if is < . /// The method is generic to avoid boxing the parameters, if they are value types. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void IsGreaterThanOrEqualTo(T value, T minimum, [CallerArgumentExpression("value")] string name = "") where T : notnull, IComparable { if (value.CompareTo(minimum) >= 0) { return; } ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsGreaterThanOrEqualTo(value, minimum, name); } /// /// Asserts that the input value must be in a given range. /// /// The type of input values to compare. /// The input value to test. /// The inclusive minimum value that is accepted. /// The exclusive maximum value that is accepted. /// The name of the input parameter being tested. /// Thrown if is < or >= . /// /// This API asserts the equivalent of " in [, )", using arithmetic notation. /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void IsInRange(T value, T minimum, T maximum, [CallerArgumentExpression("value")] string name = "") where T : notnull, IComparable { if (value.CompareTo(minimum) >= 0 && value.CompareTo(maximum) < 0) { return; } ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsInRange(value, minimum, maximum, name); } /// /// Asserts that the input value must not be in a given range. /// /// The type of input values to compare. /// The input value to test. /// The inclusive minimum value that is accepted. /// The exclusive maximum value that is accepted. /// The name of the input parameter being tested. /// Thrown if is >= or < . /// /// This API asserts the equivalent of " not in [, )", using arithmetic notation. /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void IsNotInRange(T value, T minimum, T maximum, [CallerArgumentExpression("value")] string name = "") where T : notnull, IComparable { if (value.CompareTo(minimum) < 0 || value.CompareTo(maximum) >= 0) { return; } ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsNotInRange(value, minimum, maximum, name); } /// /// Asserts that the input value must be in a given interval. /// /// The type of input values to compare. /// The input value to test. /// The exclusive minimum value that is accepted. /// The exclusive maximum value that is accepted. /// The name of the input parameter being tested. /// Thrown if is <= or >= . /// /// This API asserts the equivalent of " in (, )", using arithmetic notation. /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void IsBetween(T value, T minimum, T maximum, [CallerArgumentExpression("value")] string name = "") where T : notnull, IComparable { if (value.CompareTo(minimum) > 0 && value.CompareTo(maximum) < 0) { return; } ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsBetween(value, minimum, maximum, name); } /// /// Asserts that the input value must not be in a given interval. /// /// The type of input values to compare. /// The input value to test. /// The exclusive minimum value that is accepted. /// The exclusive maximum value that is accepted. /// The name of the input parameter being tested. /// Thrown if is > or < . /// /// This API asserts the equivalent of " not in (, )", using arithmetic notation. /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void IsNotBetween(T value, T minimum, T maximum, [CallerArgumentExpression("value")] string name = "") where T : notnull, IComparable { if (value.CompareTo(minimum) <= 0 || value.CompareTo(maximum) >= 0) { return; } ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsNotBetween(value, minimum, maximum, name); } /// /// Asserts that the input value must be in a given interval. /// /// The type of input values to compare. /// The input value to test. /// The inclusive minimum value that is accepted. /// The inclusive maximum value that is accepted. /// The name of the input parameter being tested. /// Thrown if is < or > . /// /// This API asserts the equivalent of " in [, ]", using arithmetic notation. /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void IsBetweenOrEqualTo(T value, T minimum, T maximum, [CallerArgumentExpression("value")] string name = "") where T : notnull, IComparable { if (value.CompareTo(minimum) >= 0 && value.CompareTo(maximum) <= 0) { return; } ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsBetweenOrEqualTo(value, minimum, maximum, name); } /// /// Asserts that the input value must not be in a given interval. /// /// The type of input values to compare. /// The input value to test. /// The inclusive minimum value that is accepted. /// The inclusive maximum value that is accepted. /// The name of the input parameter being tested. /// Thrown if is >= or <= . /// /// This API asserts the equivalent of " not in [, ]", using arithmetic notation. /// The method is generic to avoid boxing the parameters, if they are value types. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void IsNotBetweenOrEqualTo(T value, T minimum, T maximum, [CallerArgumentExpression("value")] string name = "") where T : notnull, IComparable { if (value.CompareTo(minimum) < 0 || value.CompareTo(maximum) > 0) { return; } ThrowHelper.ThrowArgumentOutOfRangeExceptionForIsNotBetweenOrEqualTo(value, minimum, maximum, name); } }