代码之家  ›  专栏  ›  技术社区  ›  Eric J.

通用C代码和加号运算符[副本]

  •  21
  • Eric J.  · 技术社区  · 13 年前

    我正在编写一个类,它基本上为C中的每一个基元数值类型执行相同的计算类型。虽然实际计算更复杂,但可以将其视为计算多个值平均值的方法,例如:

    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/的类执行相同的操作,我的第一个想法是简单地使用泛型:

    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++;
        }
    }
    

    不幸的是,C无法确定T是否支持运算符+和/所以不编译上述代码段。我的下一个想法是将t约束到支持这些操作符的类型,但是我的初步研究表明这是不可能的。

    当然可以将我想要支持的每种类型装箱在一个实现自定义接口的类中,例如imath,并将t限制为该接口,但是这个代码将被多次调用,我希望避免装箱开销。

    有没有一种优雅而有效的方法来解决这个问题而不需要重复代码?

    4 回复  |  直到 5 年前
        1
  •  14
  •   tukaef    5 年前

    最后我使用了表达式,这是MarcGravell概述的一种方法,我通过跟踪Spinon评论的链接发现了这种方法。

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

        2
  •  4
  •   xanatos    9 年前

    (不好意思,如果我今天发的话,我想找个地方放这段代码,这个问题似乎很完美)

    作为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> 将已完全初始化。

    在一些简单的基准测试中,它比直接加法慢2倍。我觉得很好。啊…它与重新定义 operator+ . 显然,构建其他操作很容易。

    梅里奥·休斯的增刊

    方法可以通过元编码进行扩展,这样您就可以处理 T1 操作 T2 . 例如,这里如果 T1 是一个数字,然后需要将其转换为 T2 == double 第一个在 operator * 然后把它转换回来。而当 T1 Foo 有运算符要与乘 T2=双 您可以省略转换。这个 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
  •  2
  •   Johann Blais    13 年前

    在C 4.0中有一种使用动态的方法,它显然不是完美的,但它可以为这个问题带来新的光明。

    细节是 in this blog post

        4
  •  1
  •   Eric J.    12 年前

    我发现了另一个有趣的方法,它比我最初使用的表达式树解决方案更易于编码和调试:

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

    此解决方案以一种有趣的方式使用泛型类型约束,以确保支持所有必需的操作,但不引入任何装箱或虚拟方法调用。