Подтвердить что ты не робот

Общий код С# и оператор Plus

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

class Calc
{
    public int Count { get; private set; }
    public int Total { get; private set; }
    public int Average { get { return Count / Total; } }
    public int AddDataPoint(int data)
    {
        Total += data;
        Count++;
    }
}

Теперь, чтобы поддержать ту же операцию для double, float и, возможно, других классов, которые определяют operator + и operator/, моя первая мысль состояла в том, чтобы просто использовать generics:

class Calc<T>
{
    public T Count { get; private set; }
    public T Total { get; private set; }
    public T Average { get { return Count / Total; } }
    public T AddDataPoint(T data)
    {
        Total += data;
        Count++;
    }
}

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

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

Есть ли элегантный и эффективный способ решить эту проблему без дублирования кода?

4b9b3361

Ответ 1

Я закончил тем, что использовал Expressions, подход, обрисованный Марком Грэвеллом, который я нашел, перейдя по ссылкам из комментариев спинона.

https://jonskeet.uk/csharp/miscutil/usage/genericoperators.html

Ответ 2

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

Как расширение статьи Gravell:

public static class Add<T>
{
    public static readonly Func<T, T, T> Do;

    static Add()
    {
        var par1 = Expression.Parameter(typeof(T));
        var par2 = Expression.Parameter(typeof(T));

        var add = Expression.Add(par1, par2);

        Do = Expression.Lambda<Func<T, T, T>>(add, par1, par2).Compile();
    }
}

Вы используете его как:

int sum = Add<int>.Do(x, y);

Преимущество состоит в том, что мы используем систему типов .NET для хранения различных "вариантов" Add и при необходимости создаем новые. Поэтому при первом вызове Add<int>.Do(...) будет создан Expression, но если вы вызовете его второй раз, Add<int> уже будет полностью инициализирован.

В некоторых простых тестах он 2x медленнее прямого добавления. Я думаю, это очень хорошо. Ах... он совместим с объектами, которые переопределяют operator+. Очевидно, что создание других операций легко.

Дополнение от Мейриона Хьюза

Метод может быть расширен с помощью метакодирования, поэтому вы можете обрабатывать случаи операции T1 T2. Например, здесь, если T1 - это число, тогда его нужно преобразовать в T2 == double перед тем, как operator * затем преобразует его обратно. Если T1 есть Foo, а Foo имеет оператор для умножения с T2 == double, вы можете опустить преобразование. try, catch необходимо, потому что это самый простой способ проверить наличие T operator *(T, double).

public static class Scale<T>
{
    public static Func<T, double, T> Do { get; private set; }

    static Scale()
    {
        var par1 = Expression.Parameter(typeof(T));
        var par2 = Expression.Parameter(typeof(double));

        try
        {
            Do = Expression
                .Lambda<Func<T, double, T>>(
                    Expression.Multiply(par1, par2),
                    par1, par2)
                .Compile();
        }
        catch
        {
            Do = Expression
                .Lambda<Func<T, double, T>>(
                    Expression.Convert(
                        Expression.Multiply(
                            Expression.Convert(par1, typeof (double)),
                            par2),
                        typeof(T)),
                    par1, par2)
                .Compile();
        }
    }
}

Ответ 3

В С# 4.0 используется подход, использующий динамику, но он не является совершенным, но он может принести новый свет.

Подробности в этом сообщении в блоге

Ответ 4

Я нашел еще один интересный подход, который легче кодировать и отлаживать, чем ядро ​​из дерева решений, которое я изначально использовал:

http://www.codeproject.com/KB/cs/genericnumerics.aspx

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