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

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

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

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

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

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

Ответы [ 20 ]

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

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

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

90 голосов
/ 15 марта 2014

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

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

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

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

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

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

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

82 голосов
/ 28 августа 2008

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

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

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 .


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

55 голосов
/ 29 января 2011

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

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 структура.

15 голосов
/ 13 августа 2009

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

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

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

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

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

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

dynamic x = ..., y = ...
dynamic result = x + y; // does what you expect
14 голосов
/ 28 августа 2008

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

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

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

11 голосов
/ 28 августа 2008

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

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>

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

4 голосов
/ 23 декабря 2014

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

    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;
        }
3 голосов
/ 10 мая 2013

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

Вместо:

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

2 голосов
/ 28 августа 2008

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

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

...