Проверьте, равно ли значение 0 с помощью метода расширения

У меня есть метод расширения, который выглядит как

public static T ThrowIfObjectIsNull<T>(this T argument) where T : class
{
  if (argument == null)
      throw new ArgumentNullException(nameof(argument));

   return argument;
}

Это в основном проверяет, не передается ли объект, который передается. То, что я пытаюсь сделать, это создать другой метод расширения, в котором значение int которое передается, не равно 0. Поэтому я пошел и создал:

public static T ThrowIfZero<T>(this T argument) where T : struct
{
   if (argument == 0)
     throw new ArgumentOutOfRangeException("some error here");

   return argument;
}

Конечно, вышесказанное не компилирует, предлагая ошибку:

Ошибка CS0019 Оператор '==' не может применяться к операндам типа 'T' и 'int'

Может ли кто-нибудь указать мне в правильном направлении, как я могу проверить, не значение аргумента не 0?

Ответ 1

Вы можете просто использовать Equals:

public static T ThrowIfZero<T>(this T argument) where T : struct
{
    if (argument.Equals(0))
        throw new ArgumentOutOfRangeException("some error here");

    return argument;
}

Но это не очень хорошо работает, если аргументом является, например, десятичный 0.0m который не равен целому числу 0 поскольку Питер правильно прокомментировал.

Поэтому, если вам нужна версия, которая работает для любого номера, вы можете использовать этот подход:

public static T ThrowIfZero<T>(this T argument) where T : struct
{
    bool isZero = Decimal.Compare(0.0m, Convert.ToDecimal(argument)) == 0;
    if (isZero)
        throw new ArgumentOutOfRangeException("some error here");

    return argument;
}

Ответ 2

Вы также можете использовать EqualityComparer.

public static T ThrowIfZero<T>(this T argument) where T : struct
{   
     if (EqualityComparer<T>.Default.Equals(argument, default(T)))   
        throw new ArgumentOutOfRangeException("some error here");

     return argument;
}

Вы можете направить ответ на этот пост (кредит должен идти на @Mehrdad).

Ответ 3

Это не похоже, что вам нужны дженерики вообще. Если переменная является просто int, как вы предлагаете, просто используйте:

public static int ThrowIfZero(this int argument)
{
    if (argument == 0)
    {
        throw new ArgumentOutOfRangeException("some error here");
    }

    return argument;
}

Ответ 4

int, decimal и т.д. реализуют IComparable поэтому что-то вроде этого также работает:

public static T ThrowIfZero<T>(this T argument) 
    where T : struct, IComparable
{
   if (argument.CompareTo(default(T)) == 0)
     throw new ArgumentOutOfRangeException("some error here");

   return argument;
}

Ответ 5

Еще один способ, который я видел недавно:

public static int ThrowIfZero<T>(this T param)
    where T : struct
{
    var o = (object)param;
    int i;

    try   { i = (int)o; }
    catch { throw new ArgumentException("Param must be of type int");  }

    if (i == 0) throw new ArgumentException("Must be not be zero");

    return (int)(object)param;
}

Мы можем обмануть компилятор, позволив нам направить T в int, сначала набросив его на object. Это отлично работает для int, но недостатком является то, что это работает только для int и не будет работать для float.

Если вы хотите, чтобы он работал со всеми численными типами, вы можете использовать сопоставление с образцом и сделать что-то вроде этого:

public static T ThrowIfZero<T>(this T param)
    where T : struct
{
    switch (param)
    {
        case int     i:  if (i == 0)  throwException(); break;
        case double  d:  if (d == 0)  throwException(); break;
        case float   f:  if (f == 0)  throwException(); break;
        case decimal c:  if (c == 0)  throwException(); break;
        case long    l:  if (l == 0)  throwException(); break;
        case uint    ui: if (ui == 0) throwException(); break;
        case ulong   ul: if (ul == 0) throwException(); break;
        case byte    b:  if (b == 0)  throwException(); break;
        default: throw new ArgumentException("Invalid Type");
    }

    return param;

    // ---- Local Functions ---- //
    void throwException() => throw new ArgumentException("Must not be zero");
}

Конечно, лучшим решением было бы, если бы они взяли Джона Скита в свое предложение и сделали там, where T: numeric что ограничивает его в основном вышеприведенными типами и, возможно, некоторыми настраиваемыми типами, поэтому мы можем включить наш собственный ComplexNumber или SuperBigInteger.

Честно говоря, я бы не делал этого ни одним из этих способов, потому что первый способ кастинга, а второй способ просто приводит к отсутствию дел и необходимости поддерживать switch если вам нужно, просто подумал, что я покажу больше вариантов, хоть.