Может кто-нибудь сказать мне, есть ли способ с обобщениями ограничить аргумент универсального типа T
только:
Int16
Int32
Int64
UInt16
UInt32
UInt64
Я знаю where
ключевое слово, но не могу найти интерфейс только для этих типов,
Что-то вроде:
static bool IntegerFunction<T>(T value) where T : INumeric
c#
generics
constraints
Корин Блэйки
источник
источник
Ответы:
C # не поддерживает это. Хейлсберг описал причины неиспользования этой функции в интервью с Брюсом Эккелем :
Однако это приводит к довольно запутанному коду, в котором пользователь должен предоставить свою собственную
Calculator<T>
реализацию для каждого,T
который он хочет использовать. Пока он не должен быть расширяемым, т.е. если вы просто хотите поддерживать фиксированное количество типов, таких какint
иdouble
, вы можете обойтись без относительно простого интерфейса:( Минимальная реализация в GitHub Gist. )
Однако, как только вы захотите, чтобы пользователь мог предоставлять свои собственные, пользовательские типы, вам нужно открыть эту реализацию, чтобы пользователь мог предоставить свои собственные
Calculator
экземпляры. Например, чтобы создать экземпляр матрицы, использующей пользовательскую реализацию с десятичной плавающей запятойDFP
, вам нужно написать следующий код:... и внедрить всех членов для
DfpCalculator : ICalculator<DFP>
.Альтернатива, которая, к сожалению, имеет те же ограничения, - это работать с классами политики, как обсуждалось в ответе Сергея Шандара .
источник
Operator
/Operator<T>
; yoda.arachsys.com/csharp/miscutil/usage/genericoperators.htmlOperator<T>
коде (поскольку интервью было дано задолго до существованияExpressions
фреймворка, хотя можно было конечно пользуюсьReflection.Emit
) - и мне бы очень хотелось его обойти.Учитывая популярность этого вопроса и интерес к такой функции, я с удивлением вижу, что пока нет ответа, связанного с T4.
В этом примере кода я продемонстрирую очень простой пример того, как вы можете использовать мощный шаблонизатор, чтобы делать то, что компилятор в значительной степени делает за кулисами с обобщениями.
Вместо того, чтобы идти по кругу и жертвовать определенностью времени компиляции, вы можете просто сгенерировать нужную функцию для каждого типа, который вам нравится, и использовать ее соответствующим образом (во время компиляции!).
Для этого:
Вот и все. Вы сделали сейчас.
Сохранение этого файла автоматически скомпилирует его в этот исходный файл:
В вашем
main
методе вы можете проверить, что у вас есть уверенность во время компиляции:Я опередил одно замечание: нет, это не нарушение принципа СУХОЙ. Принцип СУХОГО заключается в том, чтобы не допустить дублирования кода людьми в нескольких местах, что может затруднить сопровождение приложения.
Это совсем не так: если вы хотите изменить, вы можете просто изменить шаблон (единый источник для всего вашего поколения!), И все готово.
Чтобы использовать его с вашими собственными пользовательскими определениями, добавьте объявление пространства имен (убедитесь, что оно совпадает с тем, в котором вы определите свою собственную реализацию) к вашему сгенерированному коду и пометьте класс как
partial
. Затем добавьте эти строки в файл шаблона, чтобы он был включен в возможную компиляцию:Давайте будем честными: это довольно круто.
Отказ от ответственности: этот образец находился под сильным влиянием метапрограммирования в .NET Кевина Хаззарда и Джейсона Бока, Manning Publications .
источник
T
который наследуется от различныхIntX
классов? Мне нравится это решение, потому что оно экономит время, но оно позволяет решить проблему на 100% (хотя и не так хорошо, как если бы C # имел встроенную поддержку этого типа ограничений), каждый из сгенерированных методов должен быть универсальным, чтобы они могут возвращать объект типа, который наследуется от одного изIntXX
классов.IntXX
типы являются структурами, что означает, что они не поддерживают наследование в первую очередь . И даже если это так, то применяется принцип подстановки Лискова (который вы, возможно, знаете из идиомы SOLID): если метод определен какX
иY
является потомком,X
то по определению любойY
может быть передан этому методу в качестве замены его базовый тип.Там нет никаких ограничений для этого. Это реальная проблема для тех, кто хочет использовать дженерики для численных расчетов.
Я бы пошел дальше и сказал, что нам нужно
Или даже
К сожалению, у вас есть только интерфейсы, базовые классы и ключевые слова
struct
(должен быть тип-значение),class
(должен быть ссылочный тип) иnew()
(должен иметь конструктор по умолчанию)Вы можете заключить число во что-то еще (подобное
INullable<T>
), как здесь, в codeproject .Вы можете применить ограничение во время выполнения (отражая для операторов или проверяя типы), но это теряет преимущество наличия универсального во-первых.
источник
where T : operators( +, -, /, * )
это законно C #? Извините за вопрос новичка.where T : operators( +, -, /, * )
, но не можем.Обходной путь с использованием политик:
Алгоритмы:
Применение:
Решение безопасно во время компиляции. CityLizard Framework предоставляет скомпилированную версию для .NET 4.0. Это файл lib / NETFramework4.0 / CityLizard.Policy.dll.
Это также доступно в Nuget: https://www.nuget.org/packages/CityLizard/ . См. Структуру CityLizard.Policy.I .
источник
struct
? Что делать, если я использую синглтон-класс вместо этого и изменить экземпляр на,public static NumericPolicies Instance = new NumericPolicies();
а затем добавить этот конструкторprivate NumericPolicies() { }
.T Add<T> (T t1, T t2)
, ноSum()
работает только тогда, когда оно может извлечь свой собственный тип T из его параметров, что невозможно когда он встроен в другую универсальную функцию.Этот вопрос является чем-то вроде часто задаваемых вопросов, поэтому я публикую его как вики (поскольку я уже публиковал подобное ранее, но это более старый вопрос); тем не мение...
Какую версию .NET вы используете? Если вы используете .NET 3.5, то у меня есть универсальная реализация операторов в MiscUtil (бесплатно и т. Д.).
У этого есть методы как
T Add<T>(T x, T y)
, и другие варианты для арифметики на различных типах (какDateTime + TimeSpan
).Кроме того, это работает для всех встроенных, поднятых и сделанных на заказ операторов и кэширует делегата для производительности.
Некоторые дополнительные сведения о том, почему это сложно, здесь .
Возможно, вы также захотите узнать, что
dynamic
(4.0) решение этой проблемы тоже косвенно - т.е.источник
К сожалению, вы можете указать структуру только в предложении where в этом случае. Кажется странным, что вы не можете конкретно указать Int16, Int32 и т. Д., Но я уверен, что есть некоторая глубокая причина реализации, лежащая в основе решения не разрешать типы значений в предложении where.
Я полагаю, что единственным решением является проверка во время выполнения, которая, к сожалению, предотвращает обнаружение проблемы во время компиляции. Это будет что-то вроде: -
Что немного некрасиво, я знаю, но, по крайней мере, обеспечивает необходимые ограничения.
Я также рассмотрю возможные последствия для производительности для этой реализации, возможно, есть более быстрый выход.
источник
// Rest of code...
может не скомпилироваться, если это зависит от операций, определенных ограничениями.// Rest of code...
likevalue + value
илиvalue * value
, вы получаете ошибку компиляции.Вероятно, самое близкое, что вы можете сделать, это
Не уверен, что вы могли бы сделать следующее
Для чего-то такого особенного, почему бы просто не иметь перегрузки для каждого типа, список такой короткий, и, возможно, он будет занимать меньше памяти.
источник
Начиная с C # 7.3, вы можете использовать более близкое приближение - неуправляемое ограничение, чтобы указать, что параметр типа является не указателем, не обнуляемым неуправляемым типом.
Неуправляемое ограничение подразумевает ограничение структуры и не может быть объединено ни с ограничениями структуры, ни с новыми ().
Тип - это неуправляемый тип, если он является одним из следующих типов:
Для дальнейшего ограничения и исключения указателей и пользовательских типов, которые не реализуют IComparable, добавьте IComparable (но enum по-прежнему является производным от IComparable, поэтому ограничьте enum, добавив IEquatable <T>, вы можете пойти дальше в зависимости от ваших обстоятельств и добавить дополнительные интерфейсы. unmanaged позволяет сохранить этот список короче):
источник
DateTime
попадает подunmanaged, IComparable, IEquatable<T>
ограничение ..Не существует способа ограничить шаблоны типами, но вы можете определять различные действия в зависимости от типа. Как часть универсального числового пакета, мне нужен универсальный класс, чтобы добавить два значения.
Обратите внимание, что typeofs оцениваются во время компиляции, поэтому операторы if будут удалены компилятором. Компилятор также удаляет ложные приведения. Так что-то будет разрешено в компиляторе
источник
Я создал небольшую библиотечную функциональность для решения этих проблем:
Вместо:
Вы могли бы написать:
Вы можете найти исходный код здесь: /codereview/26022/improvement-requested-for-generic-calculator-and-generic-number
источник
Мне было так же интересно, как Самудсону, почему только целым числам? и если это так, вы можете создать вспомогательный класс или что-то подобное для хранения всех типов, которые вы хотите.
Если все, что вы хотите, это целые числа, не используйте универсальный, который не является универсальным; или еще лучше, отклоните любой другой тип, проверив его тип.
источник
Для этого пока нет «хорошего» решения. Однако вы можете значительно сузить аргумент типа, чтобы исключить множество ошибок для вашего гипотетического ограничения «INumeric», как показано выше в Haacked.
static bool IntegerFunction <T> (значение T), где T: IComparable, IFormattable, IConvertible, IComparable <T>, IEquatable <T>, struct {...
источник
Если вы используете .NET 4.0 и более поздние версии, вы можете просто использовать динамический как аргумент метода и проверить во время выполнения, что переданный динамический тип аргумента является числовым / целочисленным типом.
Если тип переданной динамической переменной не является числовым / целочисленным типом, тогда генерируется исключение.
Пример короткого кода, который реализует идею, выглядит примерно так:
Конечно, это решение работает только во время выполнения, но никогда во время компиляции.
Если вам нужно решение, которое всегда работает во время компиляции, а не во время выполнения, вам придется обернуть динамическую переменную с помощью общедоступной структуры / класса, перегруженный public конструкторы принимают аргументы только нужных типов и дают подходящее имя для структуры / класса.
Имеет смысл, что динамическая оболочка всегда является закрытым членом класса / структуры и является единственным членом структуры / класса, а именем единственного члена структуры / класса является «значение».
Вы также должны будете определить и реализовать открытые методы и / или операторы, которые работают с желаемыми типами для частного динамического члена класса / структуры, если это необходимо.
Также имеет смысл, что структура / класс имеет специальный / уникальный конструктор, который принимает динамический в качестве аргумента, который инициализирует только свой закрытый динамический член, называемый «значением», но модификатор этого конструктора является приватным разумеется, .
Как только класс / структура будет готова, определите тип аргумента IntegerFunction, который будет тем классом / структурой, который был определен.
Пример длинного кода, который реализует идею, выглядит примерно так:
Обратите внимание, что для использования динамического в вашем коде вы должны добавить ссылку на Microsoft.CSharp
Если версия .NET Framework ниже / ниже / меньше 4.0 и динамический не определен в этой версии, вам придется использовать вместо него объект и выполнять приведение к целочисленному типу, что является проблемой, поэтому я рекомендую использовать по адресу по крайней мере .NET 4.0 или новее, если вы можете, так что вы можете использовать динамический вместо объекта .
источник
К сожалению, .NET не предоставляет способ сделать это изначально.
Чтобы решить эту проблему, я создал библиотеку OSS Genumerics, которая предоставляет большинство стандартных числовых операций для следующих встроенных числовых типов и их обнуляемых эквивалентов с возможностью добавления поддержки других числовых типов.
sbyte
,byte
,short
,ushort
,int
,uint
,long
,ulong
,float
,double
,decimal
, ИBigInteger
Производительность эквивалентна конкретному числовому типу решения, позволяющего создавать эффективные универсальные числовые алгоритмы.
Вот пример использования кода.
источник
Какой смысл упражнения?
Как уже указывали люди, у вас может быть неуниверсальная функция, берущая самый большой элемент, и компилятор автоматически преобразует меньшие целые для вас.
Если ваша функция находится на критическом для производительности пути (очень маловероятно, IMO), вы можете обеспечить перегрузки для всех необходимых функций.
источник
Я бы использовал общий, который вы могли бы обработать извне ...
источник
Это ограничение затронуло меня, когда я попытался перегрузить операторы для универсальных типов; поскольку не было никакого ограничения «INumeric», и по множеству других причин хорошие люди в stackoverflow рады предоставить, операции не могут быть определены на универсальных типах.
Я хотел что-то вроде
Я работал над этой проблемой, используя динамическую типизацию .net4.
Две вещи об использовании
dynamic
:источник
Числовые примитивные типы .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()
метод). Этот подход утратил бы преимущества, связанные с проверкой типов во время компиляции, но все же смог бы избежать операций упаковки, и каждый тип должен был бы быть "проверен" только один раз. После этого операции, связанные с этим типом, будут заменены отправкой делегата.источник
Int64
результата, но не предоставляет средства, с помощью которого, например, целое число произвольного типа может быть увеличено для получения другого целого числа того же типа .У меня была похожая ситуация, когда мне нужно было обрабатывать числовые типы и строки; кажется немного странной смесью, но вы идете.
Опять же, как и многие люди, я посмотрел на ограничения и придумал кучу интерфейсов, которые он должен был поддерживать. Тем не менее, а) это не было на 100% водонепроницаемо и б), любой новичок, взглянувший на этот длинный список ограничений, сразу же был бы очень сбит с толку.
Итак, мой подход состоял в том, чтобы поместить всю мою логику в универсальный метод без ограничений, но сделать этот универсальный метод закрытым. Затем я раскрыл его с помощью открытых методов, один из которых явно обрабатывал тип, который я хотел обработать - на мой взгляд, код чистый и явный, например
источник
Если все, что вам нужно, это использовать один числовой тип , вы можете создать нечто похожее на псевдоним в C ++ с помощью
using
.Таким образом, вместо того, чтобы иметь очень общий
ты мог бы иметь
Это может позволить вам легко перейти от
double
кint
или другим , если это необходимо, но вы не могли бы использоватьComputeSomething
сdouble
иint
той же программой.Но почему бы не заменить все
double
наint
то? Потому что ваш метод может захотеть использовать, являетсяdouble
ли вводdouble
илиint
. Псевдоним позволяет точно знать, какая переменная использует динамический тип.источник
Тема старая, но для будущих читателей:
Эта функция тесно связана с тем,
Discriminated Unions
что до сих пор не реализовано в C #. Я нашел свою проблему здесь:https://github.com/dotnet/csharplang/issues/113
Эта проблема все еще открыта, и функция была запланирована для
C# 10
Тем не менее, мы должны немного подождать, но после выпуска вы можете сделать это следующим образом:
источник
Я думаю, что вы неправильно понимаете дженерики. Если операция, которую вы пытаетесь выполнить, подходит только для определенных типов данных, значит, вы не делаете что-то «общее».
Кроме того, поскольку вы хотите разрешить функции работать только с типами данных int, вам не нужна отдельная функция для каждого конкретного размера. Простое получение параметра с наибольшим конкретным типом позволит программе автоматически выгружать меньшие типы данных. (т.е. передача Int16 автоматически преобразует в Int64 при вызове).
Если вы выполняете различные операции в зависимости от фактического размера int, передаваемого в функцию, то я думаю, что вам следует серьезно пересмотреть даже попытку сделать то, что вы делаете. Если вам нужно обмануть язык, вам следует больше думать о том, чего вы пытаетесь достичь, а не о том, как делать то, что вы хотите.
В противном случае можно использовать параметр типа Object, и тогда вам придется проверить тип параметра и предпринять соответствующие действия или вызвать исключение.
источник