C # 4.0 динамический: потенциальное эффективное решение для числовых дженериков? - PullRequest
8 голосов
/ 16 февраля 2011

После того, как я сам столкнулся с этой проблемой, пытаясь реализовать универсальный Vector2<int/float/double> в C #, я провел множество исследований этой проблемы, также описанной в этом вопросе:

Меньше дженериков? Возможное решение для арифметики в C # generics

Эти ссылки содержат дополнительную справочную информацию и интересные решения:

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

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

Теперь, когда C # 4.0 вышел с новым универсальным типом dynamic, мой вопрос к блестящему SO-сообществу таков: это инструмент, который можно использовать, возможно, для создания производительного, универсального Vector / Matrix / и т.д. числовые типы?

Понятно, что Vector2 можно построить просто так:

public struct Vector2
{
    public dynamic X;
    public dynamic Y;

    public Vector2(dynamic x, dynamic y)
    {
        this.X = x;
        this.Y = y;
    }

    public static Vector2 operator+(Vector2 a, Vector2 b)
    {
        return new Vector2(a.X + b.X, a.Y + b.Y);
    }
}

но при таком подходе у нас здесь нет ограничения типа, поэтому вы можете сделать Vector2(3, 12.4572). Есть ли способ, которым мы могли бы смешивать динамические элементы с параметром типа Vector2<int> для выполнения наших математических операций, как это было бы сделано с int s?

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

1 Ответ

7 голосов
/ 16 февраля 2011

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

// Consider making this type immutable
public struct Vector2<T>
{
    public T X;
    public T Y;

    public Vector2(T x, T y)
    {
        this.X = x;
        this.Y = y;
    }

    //  The only dangerous operation on the type
    public static Vector2<T> operator +(Vector2<T> a, Vector2<T> b)
    {
        return new Vector2<T>((dynamic)a.X + b.X, (dynamic)a.Y + b.Y);
    }
}

Теперь единственной опасной операцией является добавление 2 векторов одного типа (оператор сложения должен работать как положено в аргументе типа), но все остальное совершенно безопасно для типа, так, как это должно быть. Вы не можете сделать new Vector<int>("a", 5), добавить Vector<int> и Vector<string> или назначить добавление двух Vector<int> s к Vector<string>. Обратите внимание, что ни одна из этих ошибок не была бы обнаружена во время компиляции с вашим исходным решением.

Обратите внимание:

  1. Ничто не мешает вам использовать дженерики здесь , но идет по маршруту компиляции-выражения-дерева для добавления вместо dynamic. Вызовы делегатов не бесплатны, но теоретически они должны быть быстрее, чем dynamic подход в этом случае - по крайней мере, вы избегаете упаковывать типы значений. Только вы можете сказать, будут ли они быстрыми достаточно , хотя.

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


РЕДАКТИРОВАТЬ (OP не удовлетворен производительностью dynamic здесь):

Подход с использованием дерева выражений будет выглядеть примерно так:

public struct Vector2<T>
{
    private static readonly Func<T, T, T> Add;

    // Create and cache adder delegate in the static constructor.
    // Will throw a TypeInitializationException
    // if you can't add Ts or if T + T != T 
    static Vector2()
    {
        var firstOperand = Expression.Parameter(typeof(T), "x");
        var secondOperand = Expression.Parameter(typeof(T), "y");
        var body = Expression.Add(firstOperand, secondOperand);
        Add = Expression.Lambda<Func<T, T, T>>
              (body, firstOperand, secondOperand).Compile();
    }

    public T X;
    public T Y;

    public Vector2(T x, T y)
    {
        this.X = x;
        this.Y = y;
    }

    public static Vector2<T> operator +(Vector2<T> a, Vector2<T> b)
    {
        // Delegate invocation instead of dynamic operator invocation.
        return new Vector2<T>(Add(a.X, b.X), Add(a.Y, b.Y));
    }
}
...