通用C#代码和Plus运算符

Eri*_* J. 21 c# generics math

我正在编写一个类,它对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++;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在支持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++;
    }
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,C#无法确定T是否支持运算符+和/所以不编译上述代码段.我的下一个想法是将T限制为支持这些运算符的类型,但我的初步研究表明无法做到这一点.

当然可以在实现自定义接口的类中包含我想要支持的每个类型,例如IMath和限制T,但是这个代码将被调用很多次,我想避免装箱开销.

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

Eri*_* J. 14

我最终使用了表达式,这是Marc Gravell概述的一种方法,我通过以下链接发现了spinon的评论.

http://www.yoda.arachsys.com/csharp/genericoperators.html

  • 另请注意,MiscUtils有一个预构建的`Operator`类,它提供了您可能需要的所有必要的实用方法. (3认同)

xan*_*tos 6

(对不起,如果我今天发布它,但我正在寻找放置这段代码的地方,这个问题似乎很完美)

作为 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

你可以这样使用它:

int sum = Add<int>.Do(x, y);
Run Code Online (Sandbox Code Playgroud)

优点是我们使用 .NET 的类型系统来保存各种“变体”Add并在必要时创建新的“变体”。因此,第一次调用Add<int>.Do(...)Expression被构建,但如果你第二次调用它,Add<int>将已经完全初始化。

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

梅里恩·休斯的补充

可以使用元编码来扩展方法,以便您可以处理T1 操作 T2的情况。例如,这里如果T1是一个数字,那么需要T2 == double先将其转换为operator *数字,然后再将其转换回来。而当T1isFooFoohas 运算符与 a 相乘时,T2 == double您可以省略转换。,是必要的trycatch因为这是检查 是否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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)