Есть ли ограничение, которое ограничивает мой универсальный метод числовыми типами?


364

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

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

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

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

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

3
В настоящее время существуют различные предложения C #, которые позволили бы достичь этого, но AFAIK ни одно из них не является чем-то большим, чем предварительные исследования / обсуждения. См. Исследование: формы и расширения , Исследование: роли, интерфейсы расширения и члены статического интерфейса , Чемпион "Классы типов (также известные как Концепции, Структурные общие ограничения)" и Предложение: Общие типы должны поддерживать операторы
Крис

Ответы:


140

C # не поддерживает это. Хейлсберг описал причины неиспользования этой функции в интервью с Брюсом Эккелем :

И не ясно, что дополнительная сложность стоит того небольшого дохода, который вы получаете. Если что-то, что вы хотите сделать, не поддерживается напрямую в системе ограничений, вы можете сделать это с помощью фабричного шаблона. Вы можете иметь Matrix<T>, например, и в этом Matrixвы хотели бы определить метод точечного произведения. Это, конечно, означает , что вы в конечном счете , необходимо понять , как умножить два Ts, но вы не можете сказать , что в качестве ограничения, по крайней мере, если Tесть int, doubleили float. Но то, что вы могли бы сделать, - это Matrixпринять в качестве аргумента a Calculator<T>и Calculator<T>вызвать метод multiply. Вы идете реализовать это, и вы передаете это Matrix.

Однако это приводит к довольно запутанному коду, в котором пользователь должен предоставить свою собственную Calculator<T>реализацию для каждого, Tкоторый он хочет использовать. Пока он не должен быть расширяемым, т.е. если вы просто хотите поддерживать фиксированное количество типов, таких как intи double, вы можете обойтись без относительно простого интерфейса:

var mat = new Matrix<int>(w, h);

( Минимальная реализация в GitHub Gist. )

Однако, как только вы захотите, чтобы пользователь мог предоставлять свои собственные, пользовательские типы, вам нужно открыть эту реализацию, чтобы пользователь мог предоставить свои собственные Calculatorэкземпляры. Например, чтобы создать экземпляр матрицы, использующей пользовательскую реализацию с десятичной плавающей запятой DFP, вам нужно написать следующий код:

var mat = new Matrix<DFP>(DfpCalculator.Instance, w, h);

... и внедрить всех членов для DfpCalculator : ICalculator<DFP>.

Альтернатива, которая, к сожалению, имеет те же ограничения, - это работать с классами политики, как обсуждалось в ответе Сергея Шандара .


25
Кстати, MiscUtil предоставляет универсальный класс, который делает именно это; Operator/ Operator<T>; yoda.arachsys.com/csharp/miscutil/usage/genericoperators.html
Марк

1
@Mark: хороший комментарий. Однако, чтобы было ясно, я не думаю, что Хейлсберг относился к генерации кода как к решению проблемы, как вы это делаете в Operator<T>коде (поскольку интервью было дано задолго до существования Expressionsфреймворка, хотя можно было конечно пользуюсь Reflection.Emit) - и мне бы очень хотелось его обойти.
Конрад Рудольф

@ Конрад Рудольф: Я думаю, что этот ответ на аналогичный вопрос объясняет обходной путь Хейлсберга. Другой общий класс сделан абстрактным. Так как он требует реализации другого универсального класса для каждого типа, который вы хотите поддерживать, это приведет к дублированию кода, но это означает, что вы можете создать экземпляр исходного универсального класса только с поддерживаемым типом.
Эргвун

14
Я не согласен с фразой Хейсберга: «В некотором смысле шаблоны C ++ на самом деле нетипизированы или слабо типизированы. В то время как дженерики C # строго типизированы». Это действительно маркетинг BS для продвижения C #. Строгая / слабая типизация не связана с качеством диагностики. В противном случае: интересная находка.
Себастьян Мах

100

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

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

Вместо того, чтобы идти по кругу и жертвовать определенностью времени компиляции, вы можете просто сгенерировать нужную функцию для каждого типа, который вам нравится, и использовать ее соответствующим образом (во время компиляции!).

Для этого:

  • Создайте новый файл текстового шаблона с именем GenericNumberMethodTemplate.tt .
  • Удалите автоматически сгенерированный код (вы сохраните большую его часть, но некоторые не нужны).
  • Добавьте следующий фрагмент:
<#@ template language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Core" #>

<# Type[] types = new[] {
    typeof(Int16), typeof(Int32), typeof(Int64),
    typeof(UInt16), typeof(UInt32), typeof(UInt64)
    };
#>

using System;
public static class MaxMath {
    <# foreach (var type in types) { 
    #>
        public static <#= type.Name #> Max (<#= type.Name #> val1, <#= type.Name #> val2) {
            return val1 > val2 ? val1 : val2;
        }
    <#
    } #>
}

Вот и все. Вы сделали сейчас.

Сохранение этого файла автоматически скомпилирует его в этот исходный файл:

using System;
public static class MaxMath {
    public static Int16 Max (Int16 val1, Int16 val2) {
        return val1 > val2 ? val1 : val2;
    }
    public static Int32 Max (Int32 val1, Int32 val2) {
        return val1 > val2 ? val1 : val2;
    }
    public static Int64 Max (Int64 val1, Int64 val2) {
        return val1 > val2 ? val1 : val2;
    }
    public static UInt16 Max (UInt16 val1, UInt16 val2) {
        return val1 > val2 ? val1 : val2;
    }
    public static UInt32 Max (UInt32 val1, UInt32 val2) {
        return val1 > val2 ? val1 : val2;
    }
    public static UInt64 Max (UInt64 val1, UInt64 val2) {
        return val1 > val2 ? val1 : val2;
    }
}

В вашем mainметоде вы можете проверить, что у вас есть уверенность во время компиляции:

namespace TTTTTest
{
    class Program
    {
        static void Main(string[] args)
        {
            long val1 = 5L;
            long val2 = 10L;
            Console.WriteLine(MaxMath.Max(val1, val2));
            Console.Read();
        }
    }
}

введите описание изображения здесь

Я опередил одно замечание: нет, это не нарушение принципа СУХОЙ. Принцип СУХОГО заключается в том, чтобы не допустить дублирования кода людьми в нескольких местах, что может затруднить сопровождение приложения.

Это совсем не так: если вы хотите изменить, вы можете просто изменить шаблон (единый источник для всего вашего поколения!), И все готово.

Чтобы использовать его с вашими собственными пользовательскими определениями, добавьте объявление пространства имен (убедитесь, что оно совпадает с тем, в котором вы определите свою собственную реализацию) к вашему сгенерированному коду и пометьте класс как partial. Затем добавьте эти строки в файл шаблона, чтобы он был включен в возможную компиляцию:

<#@ import namespace="TheNameSpaceYouWillUse" #>
<#@ assembly name="$(TargetPath)" #>

Давайте будем честными: это довольно круто.

Отказ от ответственности: этот образец находился под сильным влиянием метапрограммирования в .NET Кевина Хаззарда и Джейсона Бока, Manning Publications .


Это довольно круто, но можно ли изменить это решение, чтобы методы принимали какой-то универсальный тип, Tкоторый наследуется от различных IntXклассов? Мне нравится это решение, потому что оно экономит время, но оно позволяет решить проблему на 100% (хотя и не так хорошо, как если бы C # имел встроенную поддержку этого типа ограничений), каждый из сгенерированных методов должен быть универсальным, чтобы они могут возвращать объект типа, который наследуется от одного из IntXXклассов.
Захари Кнебель

1
@ZacharyKniebel: IntXXтипы являются структурами, что означает, что они не поддерживают наследование в первую очередь . И даже если это так, то применяется принцип подстановки Лискова (который вы, возможно, знаете из идиомы SOLID): если метод определен как Xи Yявляется потомком, Xто по определению любой Yможет быть передан этому методу в качестве замены его базовый тип.
Йероен Ванневел

1
Этот обходной путь с использованием политик stackoverflow.com/questions/32664/… действительно использует T4 для генерации классов.
Сергей Шандар

2
+1 за это решение, поскольку оно сохраняет эффективность работы встроенных целочисленных типов, в отличие от решений на основе политик. Вызов встроенных операторов CLR (например, Add) через дополнительный (возможно, виртуальный) метод может серьезно повлиять на производительность, если его использовать многократно (как в математических библиотеках). А поскольку число целочисленных типов является постоянным (и не может быть унаследовано), вам нужно всего лишь заново сгенерировать код для исправления ошибок.
Аттила Кленик

1
Очень круто, и я только собирался начать его использовать, тогда я вспомнил, насколько я зависим от Resharper для рефакторинга, и вы не можете переименовать рефакторинг через шаблон T4. Это не критично, но стоит задуматься.
Bradgonesurfing

86

Там нет никаких ограничений для этого. Это реальная проблема для тех, кто хочет использовать дженерики для численных расчетов.

Я бы пошел дальше и сказал, что нам нужно

static bool GenericFunction<T>(T value) 
    where T : operators( +, -, /, * )

Или даже

static bool GenericFunction<T>(T value) 
    where T : Add, Subtract

К сожалению, у вас есть только интерфейсы, базовые классы и ключевые слова struct(должен быть тип-значение), class(должен быть ссылочный тип) и new()(должен иметь конструктор по умолчанию)

Вы можете заключить число во что-то еще (подобное INullable<T>), как здесь, в codeproject .


Вы можете применить ограничение во время выполнения (отражая для операторов или проверяя типы), но это теряет преимущество наличия универсального во-первых.


2
Интересно, видели ли вы поддержку MiscUtil для универсальных операторов ... yoda.arachsys.com/csharp/miscutil/usage/genericoperators.html
Марк

10
Да - Джон Скит указал мне на них некоторое время назад (но после этого годичного ответа) - это умная идея, но я все еще хотел бы надлежащую поддержку ограничений.
Кит

1
Подождите, where T : operators( +, -, /, * )это законно C #? Извините за вопрос новичка.
kdbanman

@kdbanman Я так не думаю. Кит говорит, что C # не поддерживает то, что просит OP, и предлагает, чтобы мы могли это делать where T : operators( +, -, /, * ), но не можем.
AMTerp

62

Обходной путь с использованием политик:

interface INumericPolicy<T>
{
    T Zero();
    T Add(T a, T b);
    // add more functions here, such as multiplication etc.
}

struct NumericPolicies:
    INumericPolicy<int>,
    INumericPolicy<long>
    // add more INumericPolicy<> for different numeric types.
{
    int INumericPolicy<int>.Zero() { return 0; }
    long INumericPolicy<long>.Zero() { return 0; }
    int INumericPolicy<int>.Add(int a, int b) { return a + b; }
    long INumericPolicy<long>.Add(long a, long b) { return a + b; }
    // implement all functions from INumericPolicy<> interfaces.

    public static NumericPolicies Instance = new NumericPolicies();
}

Алгоритмы:

static class Algorithms
{
    public static T Sum<P, T>(this P p, params T[] a)
        where P: INumericPolicy<T>
    {
        var r = p.Zero();
        foreach(var i in a)
        {
            r = p.Add(r, i);
        }
        return r;
    }

}

Применение:

int i = NumericPolicies.Instance.Sum(1, 2, 3, 4, 5);
long l = NumericPolicies.Instance.Sum(1L, 2, 3, 4, 5);
NumericPolicies.Instance.Sum("www", "") // compile-time error.

Решение безопасно во время компиляции. CityLizard Framework предоставляет скомпилированную версию для .NET 4.0. Это файл lib / NETFramework4.0 / CityLizard.Policy.dll.

Это также доступно в Nuget: https://www.nuget.org/packages/CityLizard/ . См. Структуру CityLizard.Policy.I .


У меня были проблемы с этим шаблоном, когда аргументов функции меньше, чем общих параметров. Открыт stackoverflow.com/questions/36048248/…
xvan

любая причина, почему с помощью struct? Что делать, если я использую синглтон-класс вместо этого и изменить экземпляр на, public static NumericPolicies Instance = new NumericPolicies();а затем добавить этот конструктор private NumericPolicies() { }.
М.Казем Ахгари

@ M.kazemAkhgary вы можете использовать синглтон. Я предпочитаю структуру. Теоретически, он может быть оптимизирован компилятором / CLR, потому что структура не содержит информации. В случае синглтона, вы все равно будете передавать ссылку, которая может добавить дополнительное давление на GC. Еще одним преимуществом является то, что структура не может быть нулевой :-).
Сергей Шандар

Я собирался сказать, что вы нашли очень умное решение, но решение слишком ограничено для меня: я собирался использовать его T Add<T> (T t1, T t2), но Sum()работает только тогда, когда оно может извлечь свой собственный тип T из его параметров, что невозможно когда он встроен в другую универсальную функцию.
Тобиас Кнаусс

16

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

Какую версию .NET вы используете? Если вы используете .NET 3.5, то у меня есть универсальная реализация операторов в MiscUtil (бесплатно и т. Д.).

У этого есть методы как T Add<T>(T x, T y), и другие варианты для арифметики на различных типах (как DateTime + TimeSpan).

Кроме того, это работает для всех встроенных, поднятых и сделанных на заказ операторов и кэширует делегата для производительности.

Некоторые дополнительные сведения о том, почему это сложно, здесь .

Возможно, вы также захотите узнать, что dynamic(4.0) решение этой проблемы тоже косвенно - т.е.

dynamic x = ..., y = ...
dynamic result = x + y; // does what you expect

14

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

Я полагаю, что единственным решением является проверка во время выполнения, которая, к сожалению, предотвращает обнаружение проблемы во время компиляции. Это будет что-то вроде: -

static bool IntegerFunction<T>(T value) where T : struct {
  if (typeof(T) != typeof(Int16)  &&
      typeof(T) != typeof(Int32)  &&
      typeof(T) != typeof(Int64)  &&
      typeof(T) != typeof(UInt16) &&
      typeof(T) != typeof(UInt32) &&
      typeof(T) != typeof(UInt64)) {
    throw new ArgumentException(
      string.Format("Type '{0}' is not valid.", typeof(T).ToString()));
  }

  // Rest of code...
}

Что немного некрасиво, я знаю, но, по крайней мере, обеспечивает необходимые ограничения.

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


13
+1, однако, // Rest of code...может не скомпилироваться, если это зависит от операций, определенных ограничениями.
Ник

1
Convert.ToIntXX (значение) может помочь скомпилировать // остальную часть кода - по крайней мере до тех пор, пока тип возвращаемого значения IntegerFunction также не будет иметь тип T, и тогда вы окажетесь в зацеплении :-p
yoyo

-1; это не работает по причине, указанной @Nick. В тот момент, когда вы пытаетесь выполнить какие-либо арифметические операции в // Rest of code...like value + valueили value * value, вы получаете ошибку компиляции.
Марк Эмери

13

Вероятно, самое близкое, что вы можете сделать, это

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

Не уверен, что вы могли бы сделать следующее

static bool IntegerFunction<T>(T value) where T: struct, IComparable
, IFormattable, IConvertible, IComparable<T>, IEquatable<T>

Для чего-то такого особенного, почему бы просто не иметь перегрузки для каждого типа, список такой короткий, и, возможно, он будет занимать меньше памяти.


6

Начиная с C # 7.3, вы можете использовать более близкое приближение - неуправляемое ограничение, чтобы указать, что параметр типа является не указателем, не обнуляемым неуправляемым типом.

class SomeGeneric<T> where T : unmanaged
{
//...
}

Неуправляемое ограничение подразумевает ограничение структуры и не может быть объединено ни с ограничениями структуры, ни с новыми ().

Тип - это неуправляемый тип, если он является одним из следующих типов:

  • sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal или bool
  • Любой тип перечисления
  • Любой тип указателя
  • Любой определяемый пользователем тип структуры, который содержит поля только неуправляемых типов и, в C # 7.3 и более ранних версиях, не является составным типом (тип, который включает хотя бы один аргумент типа)

Для дальнейшего ограничения и исключения указателей и пользовательских типов, которые не реализуют IComparable, добавьте IComparable (но enum по-прежнему является производным от IComparable, поэтому ограничьте enum, добавив IEquatable <T>, вы можете пойти дальше в зависимости от ваших обстоятельств и добавить дополнительные интерфейсы. unmanaged позволяет сохранить этот список короче):

    class SomeGeneric<T> where T : unmanaged, IComparable, IEquatable<T>
    {
    //...
    }

Хорошо, но не достаточно ... Например, DateTimeпопадает под unmanaged, IComparable, IEquatable<T>ограничение ..
Адам Кальвет Бол

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

4

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

    class Something<TCell>
    {
        internal static TCell Sum(TCell first, TCell second)
        {
            if (typeof(TCell) == typeof(int))
                return (TCell)((object)(((int)((object)first)) + ((int)((object)second))));

            if (typeof(TCell) == typeof(double))
                return (TCell)((object)(((double)((object)first)) + ((double)((object)second))));

            return second;
        }
    }

Обратите внимание, что typeofs оцениваются во время компиляции, поэтому операторы if будут удалены компилятором. Компилятор также удаляет ложные приведения. Так что-то будет разрешено в компиляторе

        internal static int Sum(int first, int second)
        {
            return first + second;
        }

Спасибо за предоставление эмпирического решения!
zsf222

Разве это не то же самое, что создание одного и того же метода для каждого типа?
Луис

3

Я создал небольшую библиотечную функциональность для решения этих проблем:

Вместо:

public T DifficultCalculation<T>(T a, T b)
{
    T result = a * b + a; // <== WILL NOT COMPILE!
    return result;
}
Console.WriteLine(DifficultCalculation(2, 3)); // Should result in 8.

Вы могли бы написать:

public T DifficultCalculation<T>(Number<T> a, Number<T> b)
{
    Number<T> result = a * b + a;
    return (T)result;
}
Console.WriteLine(DifficultCalculation(2, 3)); // Results in 8.

Вы можете найти исходный код здесь: /codereview/26022/improvement-requested-for-generic-calculator-and-generic-number


2

Мне было так же интересно, как Самудсону, почему только целым числам? и если это так, вы можете создать вспомогательный класс или что-то подобное для хранения всех типов, которые вы хотите.

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


2

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

static bool IntegerFunction <T> (значение T), где T: IComparable, IFormattable, IConvertible, IComparable <T>, IEquatable <T>, struct {...


2

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

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

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

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.
    }

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

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

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

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

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

Как только класс / структура будет готова, определите тип аргумента 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 и динамический не определен в этой версии, вам придется использовать вместо него объект и выполнять приведение к целочисленному типу, что является проблемой, поэтому я рекомендую использовать по адресу по крайней мере .NET 4.0 или новее, если вы можете, так что вы можете использовать динамический вместо объекта .


2

К сожалению, .NET не предоставляет способ сделать это изначально.

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

sbyte, byte, short,ushort ,int , uint, long, ulong, float, double, decimal, ИBigInteger

Производительность эквивалентна конкретному числовому типу решения, позволяющего создавать эффективные универсальные числовые алгоритмы.

Вот пример использования кода.

public static T Sum(T[] items)
{
    T sum = Number.Zero<T>();
    foreach (T item in items)
    {
        sum = Number.Add(sum, item);
    }
    return sum;
}
public static T SumAlt(T[] items)
{
    // implicit conversion to Number<T>
    Number<T> sum = Number.Zero<T>();
    foreach (T item in items)
    {
        // operator support
        sum += item;
    }
    // implicit conversion to T
    return sum;
}

1

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

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

static bool IntegerFunction(Int64 value) { }

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

static bool IntegerFunction(Int64 value) { }
...
static bool IntegerFunction(Int16 value) { }

1
Я много работаю с численными методами. Иногда я хочу целые числа, а иногда я хочу с плавающей точкой. Оба имеют 64-битные версии, которые являются оптимальными для скорости обработки. Преобразование между ними - ужасная идея, поскольку в любом случае есть потери. Хотя я склоняюсь к использованию двойных чисел, иногда я считаю, что лучше использовать целые числа из-за того, как они используются в других местах. Но было бы очень хорошо, когда я пишу алгоритм, чтобы сделать это один раз и оставить решение о типе в соответствии с требованиями экземпляра.
Проголосуй за кофе

1

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

/// <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

Это ограничение затронуло меня, когда я попытался перегрузить операторы для универсальных типов; поскольку не было никакого ограничения «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. Ошибки во время выполнения. Вы «бьете» компилятор, но теряете безопасность типов. Если универсальный тип не имеет определенного оператора, во время выполнения будет выдано исключение.

1

Числовые примитивные типы .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()метод). Этот подход утратил бы преимущества, связанные с проверкой типов во время компиляции, но все же смог бы избежать операций упаковки, и каждый тип должен был бы быть "проверен" только один раз. После этого операции, связанные с этим типом, будут заменены отправкой делегата.


@KenKin: IConvertible предоставляет средство, с помощью которого любое целое число может быть добавлено к другому целочисленному типу для получения, например, Int64результата, но не предоставляет средства, с помощью которого, например, целое число произвольного типа может быть увеличено для получения другого целого числа того же типа .
суперкат

1

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

Опять же, как и многие люди, я посмотрел на ограничения и придумал кучу интерфейсов, которые он должен был поддерживать. Тем не менее, а) это не было на 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

Если все, что вам нужно, это использовать один числовой тип , вы можете создать нечто похожее на псевдоним в 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. Псевдоним позволяет точно знать, какая переменная использует динамический тип.


0

Тема старая, но для будущих читателей:

Эта функция тесно связана с тем, Discriminated Unionsчто до сих пор не реализовано в C #. Я нашел свою проблему здесь:

https://github.com/dotnet/csharplang/issues/113

Эта проблема все еще открыта, и функция была запланирована для C# 10

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

static bool IntegerFunction<T>(T value) where T : Int16 | Int32 | Int64 | ...

-11

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

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

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

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


10
Рассмотрим гистограмму класса <T>. Имеет смысл позволить ему принимать универсальный параметр, чтобы компилятор мог оптимизировать его для байтов, целых чисел, двойных чисел, десятичных чисел, BigInt, ... но в то же время вы должны предотвратить создание, скажем, гистограммы <Hashset >, потому что - говоря с Троном - он не вычисляет. (буквально :))
воскресенье

15
Вы тот, кто неправильно понимает дженерики. Метапрограммирование не просто работает со значениями, которые могут быть любого возможного типа , это для работы с типами, которые соответствуют различным ограничениям .
Джим Балтер
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.