Шаблоны стиля С++ в С#, возможно в любом случае?

Я хочу иметь 2d векторных классов для каждого примитивного типа.

Прямо сейчас, чтобы обеспечить наилучшую производительность во время выполнения и иметь возможность использовать многие служебные функции, мне нужно иметь отдельный класс для каждого примитива (Vector2Int, Vector2Float, Vector2Long и т.д.).

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

Есть ли что-нибудь, что позволяет мне писать что-то вроде шаблонов С++ (или есть ли способ создать его)?

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

// compile is a keyword I just invented for compile-time generics/templates

class Vector2<T> compile T : int, float, double, long, string
{
    public T X { get; set; }
    public T Y { get; set; }

    public T GetLength() 
    {
        return Math.Sqrt(Math.Pow(X, 2) + Math.Pow(Y, 2));
    }
}

// during compilation, code will be automatically generated
// as if someone manually replaced T with the types specified after "compile T : "
/*
    VALID EXAMPLE (no compilation errors):

    autogenerated class Vector2<int>
    {
        public int X { get; set; }
        public int Y { get; set; }

        public int GetLength() 
        {
            return Math.Sqrt(Math.Pow(X, 2) + Math.Pow(Y, 2));
        }
    }



    UNVALID EXAMPLE (build failed, compilation errors):

    autogenerated class Vector2<string>
    {
        public string { get; set; } // ok
        public string { get; set; } // ok

        public string GetLength() 
        {
            return Math.Sqrt(Math.Pow(X, 2) + Math.Pow(Y, 2)); // error! string cannot be used with Math.Pow()
                                             // and Math.Sqrt doesn't accept string type
        }
    }
*/

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


Извините, что я не очень ясен, но позвольте мне объяснить, в чем проблема.

Рассмотрим использование обычных генераторов С#. Метод GetLength() не будет компилироваться, потому что все типы, которые я хочу использовать (int, float, double, long), требуют совместного использования интерфейса, который Math.Pow() должен принимать как параметр.

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


Я создал свой собственный генератор шаблонов, который генерирует код С#, написав код С#:) http://www.youtube.com/watch?v=Uz868MuVvTY

Ответ 1

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

Это можно сделать с помощью генерации кода и T4 templates, но для этого требуется генерировать код для каждого типа, основанный на совместном "шаблоне".

* Примечание. Запрос на соединение, по-видимому, заблокирован, по крайней мере, временно.

Ответ 2

Два решения этой проблемы:

  • Создайте абстрактный калькулятор класса или интерфейса [t] и реализуйте его для типов, которые вас интересуют. Передайте экземпляр калькулятора к вашим векторным классам, чтобы они могли использовать его для выполнения математических операций.

  • Используя деревья выражений, вы можете создать калькулятор статического класса [t], который имеет методы, такие как add, pow и т.д. в статическом конструкторе, вы можете скомпилировать динамические выражения и заставить статические методы вызвать эти скомпилированные лямбды. При таком подходе вам не нужно реализовывать калькулятор для каждого типа или передавать его (начиная с его статического).

Например:

public static class Calculator<T> {

   public static readonly Func<T, T, T> Add;
   public static readonly Func<T, T, T> Pow;

   static Calculator() {
       var p1 = Expression.Parameter(typeof(T));
       var p2 = Expression.Parameter(typeof(T));
       var addLambda = Expression.Lambda<Func<T, T, T>>(Expression.Add(p1, p2), p1, p2);
       Add = addLambda.Compile();

       // looks like the only Pow method on Math works for doubles
       var powMethod = typeof(Math).GetMethod("Pow", BindingFlags.Static | BindingFlags.Public);
       var powLambda = Expression.Lambda<Func<T, T, T>>(
           Expression.Convert(
               Expression.Call(
                   powMethod,
                   Expression.Convert(p1, typeof(double)),
                   Expression.Convert(p2, typeof(double)),
               ),
               typeof(T)
           ),
           p1,
           p2
       );
       Pow = powLambda.Compile();
   }
}

// and then in your class

T a, b;
var sum = Calculator<T>.Add(a, b);
var pow = Calculator<T>.Pow(a, b);