12

我想为每种原始类型提供 2d 矢量类。

现在,为了确保最佳的运行时性能并能够使用许多实用函数,我需要为每个基元(Vector2Int、Vector2Float、Vector2Long 等)创建一个单独的类。

这只是大量的复制粘贴,如果我必须进行更改,我必须记住在每个类和每个实用程序函数中都这样做。

有什么可以让我编写类似 C++ 模板的东西(或者有什么方法可以创建它)?

我创建了一个小概念来向您展示这将如何工作:

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

是否有一些聪明的方法来实现这一点,或者这完全不可能?


抱歉不是很清楚,但让我解释一下问题所在。

考虑使用普通的 C# 泛型。GetLength() 方法无法编译,因为我想使用的所有类型(int、float、double、long)都需要共享一个接口,Math.Pow() 应该接受该接口作为参数。

从字面上用类型名称替换“T”标记将解决这个问题,增加灵活性,达到手写代码的性能并加快开发速度。


我制作了自己的模板生成器,它通过编写 C# 代码来生成 C# 代码 :) http://www.youtube.com/watch?v=Uz868MuVvTY

4

2 回答 2

7

不幸的是,C# 中的泛型与 C++ 中的模板非常不同。为了实现这一点,对于不同的类型,IArithmetic必须存在一个共享接口(例如 )(这已被高度要求,但尚未实现) *,而现在框架中没有。

这可以通过代码生成和T4 模板来完成,但是它需要基于共享的“模板”为每种类型生成代码。

*注意:连接请求似乎被阻止,至少是暂时的。

于 2012-09-09T21:37:52.220 回答
5

这个问题的两个解决方案:

  1. 制作一个抽象类或接口计算器[t] 并为您关心的类型实现它。将计算器的实例传递给您的向量类,以便他们可以使用它进行数学运算。

  2. 使用表达式树,您实际上可以创建一个静态类计算器[t],它在静态构造函数中具有 add、pow 等方法,您可以编译动态表达式并让静态方法调用这些编译的 lambda。使用这种方法,您不必为每种类型实现计算器或传递它(因为它是静态的)。

例如:

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);
于 2012-09-09T21:53:55.700 回答