问题 C#中的C ++样式模板,可能以任何方式?


我希望每种基本类型都有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


1401
2017-09-09 21:20


起源

记住 X ^ 2 不代表 X为2的幂。它的意思是 X按位xred 2。至于问题,我不确定问题是什么。介意仔细阐述一下? - chris
你为什么不能只使用普通的仿制药?与Java泛型不同,它们可以 不 框值类型。
@chris哎呀,现在要修复示例代码。问题是C#泛型不如编译时模板(如C ++)那么灵活。如果我有一个Vector <T>类,我将无法使用GetLength()方法,除非我感兴趣的所有原始类型共享一个接口。使用编译时模板,编译器实际上用一个类型替换标记“T”。然后它编译新生成的类,如果出现错误,则构建失败。这允许任何东西,从原始类型的总和到具有重载运算符的自定义类型,并且不受接口共享的影响。 - Vittorio Romeo
@delnan GetLength()方法不适用于普通泛型。所有类型都需要共享一个接口。此外,向量示例只是编译时模板派上用场的众多情况之一。直接用类型名称替换“T”标记将使开发人员具有更大的灵活性,并且性能等同于手写代码。 - Vittorio Romeo
C#泛型也是“编译时”模板,在这方面没有区别。区别在于语义。 C ++模板只不过是文本替换机制,这使它们更强大。 - Ed S.


答案:


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

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

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


6
2017-09-09 21:37



IArithmetic这样的接口不存在的事实就是这里的问题。但我还可以想象,在许多情况下,编译时模板对于创建灵活的“手写性能”代码非常有用。我将研究T4模板,希望他们能提供帮助。 - Vittorio Romeo
@Vee你会发现这非常令人沮丧 - 很多人都对制作这项工作的困难感到恼火。每个选项都有点破解。 :( - Reed Copsey
是的,我现在正在研究它,复杂性并不乐观。我只是希望有办法做我在第一篇文章中描述的内容。我唯一的想法是创建一个必须在构建解决方案之前运行的应用程序。应用程序生成.cs文件,以某种方式将它们附加到解决方案,并以某种方式告诉Visual Studio使用这些新生成的文件编译解决方案。你认为这是可行的吗? - Vittorio Romeo
@Vee这实际上是T4的作用。如果你确实需要/想要这个,我会学会使用它,因为它非常有用,一旦你学会了它。 - Reed Copsey
对T4的复杂性不满意,我制作了一个模板生成器(自动将生成的文件添加到.csproj中: youtube.com/watch?v=Uz868MuVvTY - Vittorio Romeo


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

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

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

例如:

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);

4
2017-09-09 21:53