通用C#代码和Plus运算符

时间:2010-10-28 04:14:01

标签: 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++;
    }
}

现在支持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 个答案:

答案 0 :(得分:14)

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

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

答案 1 :(得分:4)

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

作为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+的对象兼容。显然,建立其他业务很容易。

来自Meirion Hughes的补充

可以使用元编码扩展方法,以便处理T1 操作 T2的案例。例如,如果T1是一个数字,则需要先在T2 == double之前将其转换为operator *,然后将其转换回来。如果T1FooFoo有运算符与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();
        }
    }
}

答案 2 :(得分:2)

在C#4.0中有一种使用动态的方法,它显然并不完美,但它可以为此事带来新的亮点。

详情为in this blog post

答案 3 :(得分:1)

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

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

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