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

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

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

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

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

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

person Corin Blaikie    schedule 28.08.2008    source источник
comment
Сейчас есть различные предложения C #, которые позволили бы это сделать, но, как ни странно, ни одно из них не выходит за рамки предварительных исследований / обсуждений. См. Исследование: формы и расширения, Исследование: роли, интерфейсы расширений и статические элементы интерфейса, Классы типов чемпионов (также известные как концепции, структурные общие ограничения) и Предложение: общие типы должны поддерживать операторы   -  person Chris Yungmann    schedule 17.10.2019


Ответы (21)


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

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

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

person Konrad Rudolph    schedule 29.08.2008
comment
кстати, MiscUtil предоставляет универсальный класс, который делает именно это; _1 _ / _ 2_; yoda.arachsys.com/csharp/miscutil/usage/genericoperators.html < / а> - person Marc Gravell; 13.08.2009
comment
@Mark: хороший комментарий. Однако, для ясности, я не думаю, что Хейлсберг имел в виду генерацию кода как решение проблемы, как вы это делаете в Operator<T> коде (поскольку интервью проводилось задолго до появления Expressions фреймворка, хотя один можно, конечно, использовать Reflection.Emit) - и меня действительно заинтересует его обходной путь. - person Konrad Rudolph; 13.08.2009
comment
@Konrad Rudolph: Думаю, это ответ к аналогичному вопросу объясняет обходной путь Хейлсберга. Другой общий класс сделан абстрактным. Поскольку для этого требуется реализовать другой универсальный класс для каждого типа, который вы хотите поддерживать, это приведет к дублированию кода, но не означает, что вы можете создать экземпляр исходного универсального класса только с поддерживаемым типом. - person Ergwun; 05.07.2011
comment
Я не согласен с фразой Хейсберга. Итак, в некотором смысле шаблоны C ++ на самом деле нетипизированы или слабо типизированы. В то время как универсальные шаблоны C # строго типизированы. . Это настоящий маркетинговый трюк для продвижения C #. Сильная / Слабая типизация не имеет отношения к качеству диагностики. В противном случае: интересная находка. - person Sebastian Mach; 06.07.2011
comment
Используйте десятичные дроби для всего и вернитесь к нужному типу. - person jjxtra; 22.07.2020
comment
@jjxtra Это не ужасное решение, но во многих случаях (и, вероятно, в большинстве случаев, когда это имеет значение) оно вызывает нетривиальное и потенциально недопустимое снижение производительности. - person Konrad Rudolph; 23.07.2020

Учитывая популярность этого вопроса и интерес к такой функции, я удивлен, увидев, что пока нет ответа, касающегося 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();
        }
    }
}

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

Забегу вперед, сделаю одно замечание: нет, это не нарушение принципа DRY. Принцип DRY предназначен для предотвращения дублирования кода людьми в нескольких местах, что может затруднить поддержку приложения.

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

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

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

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

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

person Jeroen Vannevel    schedule 15.03.2014
comment
Это довольно круто, но можно ли изменить это решение, чтобы методы принимали некоторый общий тип T, который является или наследуется от различных IntX классов? Мне нравится это решение, потому что оно экономит время, но для того, чтобы оно на 100% решало проблему (несмотря на то, что оно не так хорошо, как если бы в C # была встроенная поддержка этого типа ограничения), каждый из сгенерированных методов должен быть универсальным, чтобы они могут возвращать объект типа, который наследуется от одного из IntXX классов. - person Zachary Kniebel; 18.03.2014
comment
@ZacharyKniebel: вся идея этого решения заключается в том, что вы не создаете общий тип. За кулисами общий метод (примерно) создает метод, подобный этому: он заменяет общий параметр фактическим типом и использует его. Универсальный метод может захотеть наложить ограничения на передаваемый в него тип числовым типам, а затем создать фактические реализации с конкретным типом. Здесь мы пропускаем эту часть и сразу же создаем конкретные типы: все, что вам нужно сделать, это добавить типы, которые вы хотите, в свой список в файле шаблона и позволить ему сгенерировать конкретные типы - person Jeroen Vannevel; 18.03.2014
comment
Я приближаюсь к пределу моих знаний о том, как компилируются универсальные типы, поэтому я прошу прощения, если ошибаюсь, но если универсальный тип генерирует все эти методы за кулисами, то не должен он делать это, находя все классы, которые наследовать от ограниченных типов и генерировать аналогичный метод для каждого? Другими словами, не будет ли он искать все классы, наследующие от каждого из IntXX типов? Если я прав, то разве ваше решение не будет соответствовать желаемому, поскольку в своем текущем состоянии оно не учитывает унаследованные типы? - person Zachary Kniebel; 18.03.2014
comment
@ZacharyKniebel: типы IntXX - это структуры, что означает, что они не поддерживают наследование в первое место. И даже если это так, тогда принцип подстановки Лискова (который вы, возможно, знаете из идиомы SOLID) применяется: если метод определен как X и Y является потомком X, тогда по определению любой Y должен иметь возможность передаваться этому методу в качестве замены его базового типа. - person Jeroen Vannevel; 18.03.2014
comment
Я знаком с LSP, но это привносит в микс ограничения up-casting и down-casting, о чем я думал, когда делал предложение (поскольку возвращаемый объект будет иметь унаследованный тип, а не подтип). Однако вы на 100% правы в том, что я полностью проигнорировал тот факт, что все типы IntXX являются structs и, следовательно, не могут быть унаследованы, поэтому мои опасения не имели значения. Отличный ответ, Джерун, и спасибо за всю информацию! :) - person Zachary Kniebel; 18.03.2014
comment
Этот обходной путь с использованием политик stackoverflow.com/questions/32664/ действительно использует T4 для создания классов. - person Sergey Shandar; 03.04.2014
comment
+1 за это решение, поскольку оно сохраняет эффективность работы встроенных интегральных типов, в отличие от решений на основе политик. Вызов встроенных операторов CLR (например, Add) с помощью дополнительного (возможно, виртуального) метода может серьезно повлиять на производительность при многократном использовании (например, в математических библиотеках). А поскольку количество целочисленных типов постоянно (и не может быть унаследовано), вам нужно только регенерировать код для исправления ошибок. - person Attila Klenik; 13.02.2016
comment
Очень круто, и я как раз собирался начать его использовать, когда вспомнил, насколько я зависим от Resharper в рефакторинге, и вы не можете переименовать рефакторинг с помощью шаблона T4. Это не критично, но стоит задуматься. - person bradgonesurfing; 20.04.2016
comment
@AttilaKlenik Поскольку это генерация кода, вы можете создавать как статические функции, так и политики. Статические функции могут быть быстрее, но их нельзя использовать в общих алгоритмах. - person Sergey Shandar; 30.09.2016
comment
Это потрясающая функция. Есть ли способ использовать эту функцию для следующей цели: мне нужно просмотреть несколько файлов и извлечь все поля из каждого из этих файлов в другой класс? - person toughQuestions; 11.09.2020
comment
Также стоит упомянуть, что T4, к сожалению, недоступен для .NET Core, ср. github.com/dotnet/runtime/issues/23403 - person SommerEngineering; 26.09.2020

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

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

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


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

person Keith    schedule 28.08.2008
comment
Интересно, видели ли вы, что MiscUtil поддерживает общие операторы ... yoda .arachsys.com / csharp / miscutil / usage / genericoperators.html. - person Marc Gravell; 13.08.2009
comment
Да - Джон Скит недавно указал мне на них для чего-то еще (но после ответа этого года назад) - это умная идея, но мне все равно нужна надлежащая поддержка ограничений. - person Keith; 13.08.2009
comment
Подождите, where T : operators( +, -, /, * ) разрешен C #? Извините за вопрос новичка. - person kdbanman; 08.07.2015
comment
@kdbanman Я так не думаю. Кейт говорит, что C # не поддерживает то, что запрашивает OP, и предлагает, чтобы мы могли делать where T : operators( +, -, /, * ), но не можем. - person AMTerp; 28.03.2020

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

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.

person Sergey Shandar    schedule 28.01.2011
comment
У меня были проблемы с этим шаблоном, когда аргументов функции меньше, чем общих параметров. Открыт stackoverflow.com/questions/36048248/ - person xvan; 17.03.2016
comment
по какой причине использовать struct? что, если вместо этого я использую одноэлементный класс и изменю instance на public static NumericPolicies Instance = new NumericPolicies();, а затем добавлю этот конструктор private NumericPolicies() { }. - person M.kazem Akhgary; 16.12.2016
comment
@ M.kazemAkhgary вы можете использовать синглтон. Я предпочитаю struct. Теоретически ее можно оптимизировать с помощью компилятора / среды CLR, поскольку структура не содержит информации. В случае синглтона вы все равно передадите ссылку, что может добавить дополнительную нагрузку на сборщик мусора. Еще одно преимущество в том, что struct не может быть нулевой :-). - person Sergey Shandar; 17.12.2016
comment
Я собирался сказать, что вы нашли очень умное решение, но оно для меня слишком ограничено: я собирался использовать его в T Add<T> (T t1, T t2), но Sum() работает только тогда, когда он может получить собственный тип T из его параметров, то есть невозможно, если он встроен в другую универсальную функцию. - person Tobias Knauss; 18.07.2018

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

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

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

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

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

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

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

Но это не мешает инстанциации DateTime.

person Vlad Novakovsky    schedule 01.02.2020
comment
Красиво, но недостаточно ... Например, DateTime подпадает под ограничение unmanaged, IComparable, IEquatable<T> .. - person Adam Calvet Bohl; 23.04.2020
comment
Я знаю, но в зависимости от обстоятельств вы можете пойти дальше и добавить дополнительные интерфейсы. unmanaged позволяет сделать этот список короче. Я только что показал подход, приближение с использованием неуправляемого. В большинстве случаев этого достаточно - person Vlad Novakovsky; 22.05.2020
comment
этот подход потерпит неудачу в важных случаях, когда мы реализуем математические операции. T Mult ‹T› (T a, T b) {return a * b;} может работать только для числовых типов, а не для DateTime. - person shelbypereira; 17.07.2020
comment
В большинстве случаев этого достаточно, и их легко запомнить. Улучшает читаемость - для тех, кто читает код, смысл понятен. - person ; 03.10.2020

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

Какую версию .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
person Community    schedule 12.08.2009

К сожалению, в этом случае вы можете указать структуру только в предложении 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...
}

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

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

person ljs    schedule 28.08.2008
comment
+1, однако // Rest of code... может не компилироваться, если это зависит от операций, определенных ограничениями. - person Nick; 10.09.2012
comment
Convert.ToIntXX (value) может помочь в компиляции // Остального кода - по крайней мере, до тех пор, пока тип возвращаемого значения IntegerFunction также не будет иметь тип T, тогда вы запутались. :-п - person yoyo; 08.07.2014
comment
-1; это не работает по причине, указанной @Nick. В тот момент, когда вы пытаетесь выполнить какие-либо арифметические операции в // Rest of code..., например value + value или value * value, вы получаете ошибку компиляции. - person Mark Amery; 26.10.2017

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

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>

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

person Haacked    schedule 28.08.2008

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

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

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

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

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

static bool IntegerFunction<T>(T value) where T : Int16 | Int32 | Int64 | ...
person Arman Ebrahimpour    schedule 05.06.2020

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

    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;
        }
person Rob Deary    schedule 23.12.2014
comment
Спасибо за эмпирическое решение! - person zsf222; 24.11.2015
comment
Разве это не то же самое, что создание одного и того же метода для каждого типа? - person Luis; 02.11.2018

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

Вместо того:

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.

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

person Martin Mulder    schedule 10.05.2013

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

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

person Martin Marconcini    schedule 28.08.2008

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

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

person dmihailescu    schedule 01.07.2011

Если вы используете .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.
    }

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

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

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

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

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

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

person Community    schedule 19.08.2017

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

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

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

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

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;
}
person TylerBrinkley    schedule 30.01.2020

В чем смысл упражнения?

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

static bool IntegerFunction(Int64 value) { }

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

static bool IntegerFunction(Int64 value) { }
...
static bool IntegerFunction(Int16 value) { }
person dbkk    schedule 28.08.2008
comment
Я много работаю численными методами. Иногда мне нужны целые числа, а иногда - числа с плавающей запятой. Оба имеют 64-битные версии, оптимальные для скорости обработки. Преобразование между ними - ужасная идея, поскольку в каждом случае есть потери. Хотя я склонен использовать двойные числа, иногда мне все же лучше использовать целые числа из-за того, как они используются в других местах. Но было бы очень хорошо, когда я пишу алгоритм, чтобы сделать это один раз и оставить выбор типа на усмотрение требований экземпляра. - person VoteCoffee; 30.10.2014

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

/// <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;
}
person Marc Roussel    schedule 14.01.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. Ошибки времени выполнения. Вы «бьете» компилятор, но теряете безопасность типов. Если для универсального типа не определен оператор, во время выполнения будет выброшено исключение.
person pomeroy    schedule 15.11.2010

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

person supercat    schedule 06.05.2013
comment
@KenKin: IConvertible предоставляет средство, с помощью которого любое целое число может быть добавлено к другому целочисленному типу для получения, например, результат Int64, но не предоставляет средства, с помощью которых, например, целое число произвольного типа может быть увеличено, чтобы получить другое целое число того же типа. - person supercat; 15.08.2013

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

person user276648    schedule 07.12.2018