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

StackOverflow https://stackoverflow.com/questions/32664

  •  09-06-2019
  •  | 
  •  

Вопрос

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

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

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

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

static bool IntegerFunction<T>(T value) where T : INumeric 
Это было полезно?

Решение

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

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

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

Другие советы

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

enter image description here

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

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

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

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

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

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

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

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

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.


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

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

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 предоставляет скомпилированную версию для .NET 4.0.Файл имеет формат lib/NETFramework4.0/CityLizard.Policy.dll .

Это также доступно в Nuget: https://www.nuget.org/packages/CityLizard/.Видишь CityLizard.Политика.I структура.

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

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

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

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

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

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

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

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

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

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

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

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>

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

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

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

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

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

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

Вместо того, чтобы:

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

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

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

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

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

static bool IntegerFunction(Int64 value) { }

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

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

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

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

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

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

статическая целочисленная функция bool<T>(значение T), где T:IComparable, IFormattable, IConvertible, IComparable<T>, IEquatable<T>, struct {...

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

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

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

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

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

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

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

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

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

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

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

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

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

Обратите внимание, что для того, чтобы использовать динамичный в своем коде вы должны Добавить ссылку Для Майкрософт.CSharp ( резкость )

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top