// 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);
}
}