【问题标题】:Generic C# Code and the Plus Operator [duplicate]通用 C# 代码和加号运算符 [重复]
【发布时间】:2011-05-01 16:20:47
【问题描述】:

我正在编写一个类,它对 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 以及其他定义运算符 + 和运算符 / 的类的相同操作,我的第一个想法是简单地使用泛型:

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是否支持运算符+和/所以没有编译上面的sn-p。我的下一个想法是将 T 限制为支持这些运算符的类型,但我最初的研究表明这无法做到。

当然可以将我想要支持的每种类型装箱到一个实现自定义接口的类中,例如IMath 并将 T 限制在此范围内,但此代码将被多次调用,我想避免装箱开销。

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

【问题讨论】:

  • 你不能只使用 LINQ 吗? IEnumerable 支持 Sum 和 Average。
  • 查看其他 SO 问题:stackoverflow.com/questions/32664/…
  • @Mathias:不,我用这个作为实际计算的简化示例。

标签: c# generics math


【解决方案1】:

我最终使用了 Expressions,这是 Marc Gravell 概述的一种方法,我通过以下链接从 spinon 的评论中找到。

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

【讨论】:

  • 只要确保你缓存了代理并重新使用它们;p
  • 另请注意,MiscUtils 有一个预构建的 Operator 类,它提供了您可能需要的所有必要实用程序方法。
  • @Marc:是的,我实现了你的评论;-)
  • 为什么在 3 年多之后的路过投反对票?否决一个明显赞成的答案的理由会有所帮助。
【解决方案2】:

(不好意思今天发了,不过我一直在找地方放这段代码,这个问题好像很完美)

作为 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&lt;int&gt;.Do(...) 时,Expression 将被构建,但如果你第二次调用它,Add&lt;int&gt; 将已经完全初始化。

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

Meirion Hughes 的补充

可以使用元编码扩展方法,以便您可以处理T1 操作 T2 的情况。例如,这里如果T1 是一个数字,则需要先将其转换为T2 == double,然后再将operator * 转换回来。而当T1Foo 并且Foo 有运算符与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();
        }
    }
}

【讨论】:

  • @Meirion 我不太喜欢你所做的(通过编辑添加)......但是代码很整洁。我已经重新编辑了一点,并改变了一点解释
  • 是的,我知道,对不起。我认为它会很有用(它对我来说),我正准备将它添加为单独的答案,但是这个线程被锁定,另一个线程将是题外话。 ://
  • @MeirionHughes 下一次,把你的名字写在添加的文字上,就像我做的一样Meirion Hughes 的添加,所以很清楚作者的内容 1以及作者的内容 2
【解决方案3】:

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

详情in this blog post

【讨论】:

  • “动态”的问题在于它引入了另一个级别的间接性。我正在执行大量复杂的计算,并怀疑(但说实话我不知道)间接性会对应用的整体性能产生可衡量的影响。
【解决方案4】:

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

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

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

【讨论】:

    猜你喜欢
    • 2014-06-12
    • 2017-10-14
    • 1970-01-01
    • 1970-01-01
    • 2016-12-11
    • 2012-09-23
    • 2019-02-21
    • 2015-09-18
    • 1970-01-01
    相关资源
    最近更新 更多