Есть ли ограничение, которое ограничивает мой универсальный метод числовыми типами? - PullRequest
332 голосов
/ 28 августа 2008

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

  • Int16
  • Int32
  • Int64
  • UInt16
  • UInt32
  • UInt64

Мне известно ключевое слово where, но я не могу найти интерфейс только для этих типов,

Что-то вроде:

static bool IntegerFunction<T>(T value) where T : INumeric 

Ответы [ 20 ]

1 голос
/ 06 мая 2013

Числовые примитивные типы .NET не имеют общего интерфейса, который позволил бы их использовать для расчетов. Можно было бы определить ваши собственные интерфейсы (например, ISignedWholeNumber), которые будут выполнять такие операции, определить структуры, которые содержат один Int16, Int32 и т. Д., И реализовать эти интерфейсы, а затем иметь методы, которые принимают универсальные типы ограничено ISignedWholeNumber, но необходимость преобразования числовых значений в типы вашей структуры, вероятно, будет неприятной.

Альтернативным подходом было бы определение статического класса Int64Converter<T> со статическим свойством bool Available {get;}; и статических делегатов для Int64 GetInt64(T value), T FromInt64(Int64 value), bool TryStoreInt64(Int64 value, ref T dest). Конструктор класса может быть жестко запрограммирован для загрузки делегатов для известных типов и, возможно, использовать Reflection для проверки, реализует ли тип T методы с правильными именами и сигнатурами (в случае, если это что-то вроде структуры, содержащей Int64 представляет число, но имеет собственный метод ToString()). Этот подход утратил бы преимущества, связанные с проверкой типов во время компиляции, но все же смог бы избежать операций упаковки, и каждый тип должен был бы быть «проверен» только один раз. После этого операции, связанные с этим типом, будут заменены отправкой делегата.

1 голос
/ 01 июля 2011

Для этого пока нет «хорошего» решения. Однако вы можете значительно сузить аргумент типа, чтобы исключить множество ошибок для вашего гипотетического ограничения «INumeric», как показано выше Haacked.

static bool IntegerFunction (значение T) где T: IComparable, IFormattable, IConvertible, IComparable , IEquatable , структура {...

1 голос
/ 29 августа 2008

Какой смысл упражнения?

Как уже указывали люди, у вас может быть неуниверсальная функция, берущая самый большой элемент, и компилятор автоматически преобразует меньшие целые для вас.

static bool IntegerFunction(Int64 value) { }

Если ваша функция находится на критическом для производительности пути (очень маловероятно, IMO), вы можете обеспечить перегрузки для всех необходимых функций.

static bool IntegerFunction(Int64 value) { }
...
static bool IntegerFunction(Int16 value) { }
1 голос
/ 14 января 2010

Я бы использовал общий, который вы могли бы обработать извне ...

/// <summary>
/// Generic object copy of the same type
/// </summary>
/// <typeparam name="T">The type of object to copy</typeparam>
/// <param name="ObjectSource">The source object to copy</param>
public T CopyObject<T>(T ObjectSource)
{
    T NewObject = System.Activator.CreateInstance<T>();

    foreach (PropertyInfo p in ObjectSource.GetType().GetProperties())
        NewObject.GetType().GetProperty(p.Name).SetValue(NewObject, p.GetValue(ObjectSource, null), null);

    return NewObject;
}
1 голос
/ 19 августа 2017

Если вы используете .NET 4.0 и более поздние версии, то вы можете просто использовать dynamic в качестве аргумента метода и проверить во время выполнения , что переданный dynamic тип аргумента имеет числовой / целочисленный тип.

Если тип переданного динамического равен , а не числового / целочисленного типа, генерируется исключение.

Пример короткого кода, реализующего идею, выглядит примерно так:

using System;
public class InvalidArgumentException : Exception
{
    public InvalidArgumentException(string message) : base(message) {}
}
public class InvalidArgumentTypeException : InvalidArgumentException
{
    public InvalidArgumentTypeException(string message) : base(message) {}
}
public class ArgumentTypeNotIntegerException : InvalidArgumentTypeException
{
    public ArgumentTypeNotIntegerException(string message) : base(message) {}
}
public static class Program
{
    private static bool IntegerFunction(dynamic n)
    {
        if (n.GetType() != typeof(Int16) &&
            n.GetType() != typeof(Int32) &&
            n.GetType() != typeof(Int64) &&
            n.GetType() != typeof(UInt16) &&
            n.GetType() != typeof(UInt32) &&
            n.GetType() != typeof(UInt64))
            throw new ArgumentTypeNotIntegerException("argument type is not integer type");
        //code that implements IntegerFunction goes here
    }
    private static void Main()
    {
         Console.WriteLine("{0}",IntegerFunction(0)); //Compiles, no run time error and first line of output buffer is either "True" or "False" depends on the code that implements "Program.IntegerFunction" static method.
         Console.WriteLine("{0}",IntegerFunction("string")); //Also compiles but it is run time error and exception of type "ArgumentTypeNotIntegerException" is thrown here.
         Console.WriteLine("This is the last Console.WriteLine output"); //Never reached and executed due the run time error and the exception thrown on the second line of Program.Main static method.
    }

Конечно, это решение работает только во время выполнения, но никогда во время компиляции.

Если вам нужно решение, которое всегда работает во время компиляции, а не во время выполнения, вам придется обернуть dynamic общедоступной структурой / классом, чьи перегруженные публичные конструкторы принимают только аргументы нужных типов и присваивают структуре / классу соответствующее имя.

Имеет смысл, что обернутый динамический всегда закрытый член класса / структуры и является единственным членом структуры / класса и именем единственного члена структура / класс это "значение".

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

Также имеет смысл, что структура / класс имеет специальный / уникальный конструктор, который принимает dynamic в качестве аргумента, который инициализирует только свой закрытый динамический член с именем "value", но модификатор этого конструктора является частным , конечно.

Как только класс / структура будет готова, определите тип аргумента IntegerFunction, чтобы он был тем классом / структурой, который был определен.

Пример длинного кода, который реализует идею, выглядит примерно так:

using System;
public struct Integer
{
    private dynamic value;
    private Integer(dynamic n) { this.value = n; }
    public Integer(Int16 n) { this.value = n; }
    public Integer(Int32 n) { this.value = n; }
    public Integer(Int64 n) { this.value = n; }
    public Integer(UInt16 n) { this.value = n; }
    public Integer(UInt32 n) { this.value = n; }
    public Integer(UInt64 n) { this.value = n; }
    public Integer(Integer n) { this.value = n.value; }
    public static implicit operator Int16(Integer n) { return n.value; }
    public static implicit operator Int32(Integer n) { return n.value; }
    public static implicit operator Int64(Integer n) { return n.value; }
    public static implicit operator UInt16(Integer n) { return n.value; }
    public static implicit operator UInt32(Integer n) { return n.value; }
    public static implicit operator UInt64(Integer n) { return n.value; }
    public static Integer operator +(Integer x, Int16 y) { return new Integer(x.value + y); }
    public static Integer operator +(Integer x, Int32 y) { return new Integer(x.value + y); }
    public static Integer operator +(Integer x, Int64 y) { return new Integer(x.value + y); }
    public static Integer operator +(Integer x, UInt16 y) { return new Integer(x.value + y); }
    public static Integer operator +(Integer x, UInt32 y) { return new Integer(x.value + y); }
    public static Integer operator +(Integer x, UInt64 y) { return new Integer(x.value + y); }
    public static Integer operator -(Integer x, Int16 y) { return new Integer(x.value - y); }
    public static Integer operator -(Integer x, Int32 y) { return new Integer(x.value - y); }
    public static Integer operator -(Integer x, Int64 y) { return new Integer(x.value - y); }
    public static Integer operator -(Integer x, UInt16 y) { return new Integer(x.value - y); }
    public static Integer operator -(Integer x, UInt32 y) { return new Integer(x.value - y); }
    public static Integer operator -(Integer x, UInt64 y) { return new Integer(x.value - y); }
    public static Integer operator *(Integer x, Int16 y) { return new Integer(x.value * y); }
    public static Integer operator *(Integer x, Int32 y) { return new Integer(x.value * y); }
    public static Integer operator *(Integer x, Int64 y) { return new Integer(x.value * y); }
    public static Integer operator *(Integer x, UInt16 y) { return new Integer(x.value * y); }
    public static Integer operator *(Integer x, UInt32 y) { return new Integer(x.value * y); }
    public static Integer operator *(Integer x, UInt64 y) { return new Integer(x.value * y); }
    public static Integer operator /(Integer x, Int16 y) { return new Integer(x.value / y); }
    public static Integer operator /(Integer x, Int32 y) { return new Integer(x.value / y); }
    public static Integer operator /(Integer x, Int64 y) { return new Integer(x.value / y); }
    public static Integer operator /(Integer x, UInt16 y) { return new Integer(x.value / y); }
    public static Integer operator /(Integer x, UInt32 y) { return new Integer(x.value / y); }
    public static Integer operator /(Integer x, UInt64 y) { return new Integer(x.value / y); }
    public static Integer operator %(Integer x, Int16 y) { return new Integer(x.value % y); }
    public static Integer operator %(Integer x, Int32 y) { return new Integer(x.value % y); }
    public static Integer operator %(Integer x, Int64 y) { return new Integer(x.value % y); }
    public static Integer operator %(Integer x, UInt16 y) { return new Integer(x.value % y); }
    public static Integer operator %(Integer x, UInt32 y) { return new Integer(x.value % y); }
    public static Integer operator %(Integer x, UInt64 y) { return new Integer(x.value % y); }
    public static Integer operator +(Integer x, Integer y) { return new Integer(x.value + y.value); }
    public static Integer operator -(Integer x, Integer y) { return new Integer(x.value - y.value); }
    public static Integer operator *(Integer x, Integer y) { return new Integer(x.value * y.value); }
    public static Integer operator /(Integer x, Integer y) { return new Integer(x.value / y.value); }
    public static Integer operator %(Integer x, Integer y) { return new Integer(x.value % y.value); }
    public static bool operator ==(Integer x, Int16 y) { return x.value == y; }
    public static bool operator !=(Integer x, Int16 y) { return x.value != y; }
    public static bool operator ==(Integer x, Int32 y) { return x.value == y; }
    public static bool operator !=(Integer x, Int32 y) { return x.value != y; }
    public static bool operator ==(Integer x, Int64 y) { return x.value == y; }
    public static bool operator !=(Integer x, Int64 y) { return x.value != y; }
    public static bool operator ==(Integer x, UInt16 y) { return x.value == y; }
    public static bool operator !=(Integer x, UInt16 y) { return x.value != y; }
    public static bool operator ==(Integer x, UInt32 y) { return x.value == y; }
    public static bool operator !=(Integer x, UInt32 y) { return x.value != y; }
    public static bool operator ==(Integer x, UInt64 y) { return x.value == y; }
    public static bool operator !=(Integer x, UInt64 y) { return x.value != y; }
    public static bool operator ==(Integer x, Integer y) { return x.value == y.value; }
    public static bool operator !=(Integer x, Integer y) { return x.value != y.value; }
    public override bool Equals(object obj) { return this == (Integer)obj; }
    public override int GetHashCode() { return this.value.GetHashCode(); }
    public override string ToString() { return this.value.ToString(); }
    public static bool operator >(Integer x, Int16 y) { return x.value > y; }
    public static bool operator <(Integer x, Int16 y) { return x.value < y; }
    public static bool operator >(Integer x, Int32 y) { return x.value > y; }
    public static bool operator <(Integer x, Int32 y) { return x.value < y; }
    public static bool operator >(Integer x, Int64 y) { return x.value > y; }
    public static bool operator <(Integer x, Int64 y) { return x.value < y; }
    public static bool operator >(Integer x, UInt16 y) { return x.value > y; }
    public static bool operator <(Integer x, UInt16 y) { return x.value < y; }
    public static bool operator >(Integer x, UInt32 y) { return x.value > y; }
    public static bool operator <(Integer x, UInt32 y) { return x.value < y; }
    public static bool operator >(Integer x, UInt64 y) { return x.value > y; }
    public static bool operator <(Integer x, UInt64 y) { return x.value < y; }
    public static bool operator >(Integer x, Integer y) { return x.value > y.value; }
    public static bool operator <(Integer x, Integer y) { return x.value < y.value; }
    public static bool operator >=(Integer x, Int16 y) { return x.value >= y; }
    public static bool operator <=(Integer x, Int16 y) { return x.value <= y; }
    public static bool operator >=(Integer x, Int32 y) { return x.value >= y; }
    public static bool operator <=(Integer x, Int32 y) { return x.value <= y; }
    public static bool operator >=(Integer x, Int64 y) { return x.value >= y; }
    public static bool operator <=(Integer x, Int64 y) { return x.value <= y; }
    public static bool operator >=(Integer x, UInt16 y) { return x.value >= y; }
    public static bool operator <=(Integer x, UInt16 y) { return x.value <= y; }
    public static bool operator >=(Integer x, UInt32 y) { return x.value >= y; }
    public static bool operator <=(Integer x, UInt32 y) { return x.value <= y; }
    public static bool operator >=(Integer x, UInt64 y) { return x.value >= y; }
    public static bool operator <=(Integer x, UInt64 y) { return x.value <= y; }
    public static bool operator >=(Integer x, Integer y) { return x.value >= y.value; }
    public static bool operator <=(Integer x, Integer y) { return x.value <= y.value; }
    public static Integer operator +(Int16 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator +(Int32 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator +(Int64 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator +(UInt16 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator +(UInt32 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator +(UInt64 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator -(Int16 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator -(Int32 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator -(Int64 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator -(UInt16 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator -(UInt32 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator -(UInt64 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator *(Int16 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator *(Int32 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator *(Int64 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator *(UInt16 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator *(UInt32 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator *(UInt64 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator /(Int16 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator /(Int32 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator /(Int64 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator /(UInt16 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator /(UInt32 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator /(UInt64 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator %(Int16 x, Integer y) { return new Integer(x % y.value); }
    public static Integer operator %(Int32 x, Integer y) { return new Integer(x % y.value); }
    public static Integer operator %(Int64 x, Integer y) { return new Integer(x % y.value); }
    public static Integer operator %(UInt16 x, Integer y) { return new Integer(x % y.value); }
    public static Integer operator %(UInt32 x, Integer y) { return new Integer(x % y.value); }
    public static Integer operator %(UInt64 x, Integer y) { return new Integer(x % y.value); }
    public static bool operator ==(Int16 x, Integer y) { return x == y.value; }
    public static bool operator !=(Int16 x, Integer y) { return x != y.value; }
    public static bool operator ==(Int32 x, Integer y) { return x == y.value; }
    public static bool operator !=(Int32 x, Integer y) { return x != y.value; }
    public static bool operator ==(Int64 x, Integer y) { return x == y.value; }
    public static bool operator !=(Int64 x, Integer y) { return x != y.value; }
    public static bool operator ==(UInt16 x, Integer y) { return x == y.value; }
    public static bool operator !=(UInt16 x, Integer y) { return x != y.value; }
    public static bool operator ==(UInt32 x, Integer y) { return x == y.value; }
    public static bool operator !=(UInt32 x, Integer y) { return x != y.value; }
    public static bool operator ==(UInt64 x, Integer y) { return x == y.value; }
    public static bool operator !=(UInt64 x, Integer y) { return x != y.value; }
    public static bool operator >(Int16 x, Integer y) { return x > y.value; }
    public static bool operator <(Int16 x, Integer y) { return x < y.value; }
    public static bool operator >(Int32 x, Integer y) { return x > y.value; }
    public static bool operator <(Int32 x, Integer y) { return x < y.value; }
    public static bool operator >(Int64 x, Integer y) { return x > y.value; }
    public static bool operator <(Int64 x, Integer y) { return x < y.value; }
    public static bool operator >(UInt16 x, Integer y) { return x > y.value; }
    public static bool operator <(UInt16 x, Integer y) { return x < y.value; }
    public static bool operator >(UInt32 x, Integer y) { return x > y.value; }
    public static bool operator <(UInt32 x, Integer y) { return x < y.value; }
    public static bool operator >(UInt64 x, Integer y) { return x > y.value; }
    public static bool operator <(UInt64 x, Integer y) { return x < y.value; }
    public static bool operator >=(Int16 x, Integer y) { return x >= y.value; }
    public static bool operator <=(Int16 x, Integer y) { return x <= y.value; }
    public static bool operator >=(Int32 x, Integer y) { return x >= y.value; }
    public static bool operator <=(Int32 x, Integer y) { return x <= y.value; }
    public static bool operator >=(Int64 x, Integer y) { return x >= y.value; }
    public static bool operator <=(Int64 x, Integer y) { return x <= y.value; }
    public static bool operator >=(UInt16 x, Integer y) { return x >= y.value; }
    public static bool operator <=(UInt16 x, Integer y) { return x <= y.value; }
    public static bool operator >=(UInt32 x, Integer y) { return x >= y.value; }
    public static bool operator <=(UInt32 x, Integer y) { return x <= y.value; }
    public static bool operator >=(UInt64 x, Integer y) { return x >= y.value; }
    public static bool operator <=(UInt64 x, Integer y) { return x <= y.value; }
}
public static class Program
{
    private static bool IntegerFunction(Integer n)
    {
        //code that implements IntegerFunction goes here
        //note that there is NO code that checks the type of n in rum time, because it is NOT needed anymore 
    }
    private static void Main()
    {
        Console.WriteLine("{0}",IntegerFunction(0)); //compile error: there is no overloaded METHOD for objects of type "int" and no implicit conversion from any object, including "int", to "Integer" is known.
        Console.WriteLine("{0}",IntegerFunction(new Integer(0))); //both compiles and no run time error
        Console.WriteLine("{0}",IntegerFunction("string")); //compile error: there is no overloaded METHOD for objects of type "string" and no implicit conversion from any object, including "string", to "Integer" is known.
        Console.WriteLine("{0}",IntegerFunction(new Integer("string"))); //compile error: there is no overloaded CONSTRUCTOR for objects of type "string"
    }
}

Обратите внимание, что для использования динамического в вашем коде необходимо Добавить ссылку в Microsoft.CSharp

Если версия .NET Framework ниже / ниже / меньше 4,0 и динамическая не определена в этой версии, вам придется использовать object вместо этого и выполнить приведение к целочисленный тип, который является проблемой, поэтому я рекомендую использовать по крайней мере .NET 4.0 или новее, если вы можете, поэтому вы можете использовать динамический вместо объект .

1 голос
/ 15 ноября 2010

Это ограничение затронуло меня, когда я попытался перегрузить операторы для универсальных типов; поскольку не было никакого ограничения «INumeric», и по множеству других причин хорошие люди в stackoverflow рады предоставить, операции не могут быть определены для универсальных типов.

Я хотел что-то вроде

public struct Foo<T>
{
    public T Value{ get; private set; }

    public static Foo<T> operator +(Foo<T> LHS, Foo<T> RHS)
    {
        return new Foo<T> { Value = LHS.Value + RHS.Value; };
    }
}

Я работал над этой проблемой, используя динамическую типизацию .net4 во время выполнения.

public struct Foo<T>
{
    public T Value { get; private set; }

    public static Foo<T> operator +(Foo<T> LHS, Foo<T> RHS)
    {
        return new Foo<T> { Value = LHS.Value + (dynamic)RHS.Value };
    }
}

Две вещи об использовании dynamic:

  1. Производительность. Все типы значений упакованы.
  2. Ошибки во время выполнения. Вы «бьете» компилятор, но теряете безопасность типов. Если универсальный тип не имеет определенного оператора, во время выполнения будет выдано исключение.
0 голосов
/ 28 августа 2008

Я думаю, вы неправильно понимаете дженерики. Если операция, которую вы пытаетесь выполнить, подходит только для определенных типов данных, значит, вы не делаете что-то «общее».

Кроме того, поскольку вы хотите разрешить функции работать только с типами данных int, вам не нужна отдельная функция для каждого конкретного размера. Простое получение параметра с наибольшим конкретным типом позволит программе автоматически выгружать меньшие типы данных. (т. е. передача Int16 автоматически преобразует в Int64 при вызове).

Если вы выполняете различные операции в зависимости от фактического размера int, передаваемого в функцию, тогда я думаю, что вам следует серьезно пересмотреть даже попытку сделать то, что вы делаете. Если вам нужно обмануть язык, вам следует больше думать о том, чего вы пытаетесь достичь, а не о том, как делать то, что вы хотите.

В противном случае можно использовать параметр типа Object, а затем вам придется проверить тип параметра и предпринять соответствующее действие или вызвать исключение.

0 голосов
/ 07 декабря 2018

Если все, что вам нужно, это использовать один числовой тип , вы можете создать нечто похожее на псевдоним в C ++ с помощью using.

Так что вместо того, чтобы иметь очень общий

T ComputeSomething<T>(T value1, T value2) where T : INumeric { ... }

вы могли бы иметь

using MyNumType = System.Double;
T ComputeSomething<MyNumType>(MyNumType value1, MyNumType value2) { ... }

Это может позволить вам легко перейти от double к int или другим, если это необходимо, но вы не сможете использовать ComputeSomething с double и int в одной программе. *

Но почему бы тогда не заменить все double на int? Потому что ваш метод может захотеть использовать double независимо от того, является ли ввод double или int. Псевдоним позволяет точно узнать, какая переменная использует тип dynamic .

0 голосов
/ 10 декабря 2018

У меня была похожая ситуация, когда мне нужно было обрабатывать числовые типы и строки; кажется немного странной смесью, но вы идете.

Опять же, как и многие люди, я посмотрел на ограничения и придумал несколько интерфейсов, которые он должен был поддерживать. Тем не менее, а) это не было на 100% водонепроницаемо и б) любой, кто впервые взглянет на этот длинный список ограничений, сразу же будет очень сбит с толку.

Итак, мой подход состоял в том, чтобы поместить всю мою логику в универсальный метод без ограничений, но сделать этот универсальный метод частным. Затем я открыл его с помощью открытых методов, один из которых явно обрабатывал тип, который я хотел обработать - на мой взгляд, код является чистым и явным, например,

public static string DoSomething(this int input, ...) => DoSomethingHelper(input, ...);
public static string DoSomething(this decimal input, ...) => DoSomethingHelper(input, ...);
public static string DoSomething(this double input, ...) => DoSomethingHelper(input, ...);
public static string DoSomething(this string input, ...) => DoSomethingHelper(input, ...);

private static string DoSomethingHelper<T>(this T input, ....)
{
    // complex logic
}
0 голосов
/ 28 августа 2008

Нет единого интерфейса или базового класса, который они все наследуют (который также не наследуется другими классами), поэтому простой ответ - нет.

Интересно, почему это проблема? Что вы хотите сделать внутри своего класса IntegerFunction, что можно сделать только с целыми числами?

...