【问题标题】:Writing generic arithmetic in C#用 C# 编写泛型算术
【发布时间】:2023-06-10 18:14:01
【问题描述】:

我有一个数字列表,我编写了一个对这些数字执行一些计算的方法;总而言之,它是关于一页代码。该方法对这些数字执行一些算术和比较。

我的问题是,在一种情况下,列表是IList<byte>,在另一种情况下,是IList<float>。两种情况下的算法完全相同(是的,我知道溢出错误和精度损失等问题,但在我的情况下它有效)。我怎样才能编写一个可以处理两个列表的方法?我不能写像 void DoStuff<T>(IList<T> numbers) 这样的东西,因为没有通用的算术运算符 (+ - * /)。

一种解决方案是将所有内容简单地存储为浮点数,但我想避免它。列表很长,因此存储浮点数而不是字节会占用太多内存。我也可以执行DoStuffFloat(byteList.Select(b => (float)b)) 之类的操作,但如果可以避免的话,我也不想支付性能损失。

没有复制粘贴整个方法并将“float”替换为“byte”(反之亦然),有什么不错的解决方案吗?

编辑:我应该提到我只能在这个项目中使用 .NET 3.5。

【问题讨论】:

  • 你能贴出你的代码吗?可以吗?
  • 我们在谈论多少个列表项会消耗太多内存?
  • 很遗憾.NET 没有numerical tower。这意味着数字类型之间没有类型关系,可以让您用泛型表达这一点。
  • 与其将它们全部存储为浮点数,不如将它们全部存储为 int。放大你的浮点数,这样你就可以对它们进行 int 数学运算,然后在你取回结果时缩小结果。
  • 你应该看看这篇文章......其中一个链接包括一个实用程序类,它可以满足你的需求。 *.com/questions/1267902/…

标签: c# .net generics math


【解决方案1】:

您可以做的是创建一个包含您想要支持的操作的通用接口,创建一个通用工厂来为支持的类型创建实例以执行操作,然后使用它。

例如,

public interface IOperations<T>
{
    T Add(T a, T b);
    T Subtract(T a, T b);
    T Multiply(T a, T b);
    T Divide(T a, T b);
}

public static class Operations<T>
{
    public static IOperations<T> Default { get { return Create(); } }

    static IOperations<T> Create()
    {
        var type = typeof(T);
        switch (Type.GetTypeCode(type))
        {
        case TypeCode.Byte:
            return (IOperations<T>)new ByteOperations();
        case TypeCode.Single:
            return (IOperations<T>)new SingleOperations();
        default:
            var message = String.Format("Operations for type {0} is not supported.", type.Name);
            throw new NotSupportedException(message);
        }
    }

    class ByteOperations : IOperations<byte>
    {
        public byte Add(byte a, byte b)      { return unchecked ((byte)(a + b)); }
        public byte Subtract(byte a, byte b) { return unchecked ((byte)(a - b)); }
        public byte Multiply(byte a, byte b) { return unchecked ((byte)(a * b)); }
        public byte Divide(byte a, byte b)   { return unchecked ((byte)(a / b)); }
    }

    class SingleOperations : IOperations<float>
    {
        public float Add(float a, float b)      { return a + b; }
        public float Subtract(float a, float b) { return a - b; }
        public float Multiply(float a, float b) { return a * b; }
        public float Divide(float a, float b)   { return a / b; }
    }
}
T Mean<T>(IList<T> numbers)
{
    var operations = Operations<T>.Default;
    var sum = numbers.Aggregate(operations.Add);
    var count = (T)Convert.ChangeType(numbers.Count, typeof(T));
    return operations.Divide(sum, count);
}

var resultByte = Mean(new byte[] { 1, 2, 3, 4 });                // 2
var resultSingle = Mean(new float[] { 1.1F, 2.1F, 3.1F, 4.1F }); // 2.6F
var resultInt = Mean(new int[] { 1, 2, 3, 4 });                  // not supported

如果您不介意小的性能损失,您可以动态创建所需的操作。

class GenericOperations<T> : IOperations<T>
{
    public GenericOperations()
    {
        add = CreateLambda(Expression.Add);
        subtract = CreateLambda(Expression.Subtract);
        multiply = CreateLambda(Expression.Multiply);
        divide = CreateLambda(Expression.Divide);
    }
    private Func<T, T, T> add, subtract, multiply, divide;
    private static Func<T, T, T> CreateLambda(Func<Expression, Expression, BinaryExpression> op)
    {
        var a = Expression.Parameter(typeof(T), "a");
        var b = Expression.Parameter(typeof(T), "b");
        var body = op(a, b);
        var expr = Expression.Lambda<Func<T, T, T>>(body, a, b);
        return expr.Compile();
    }

    public T Add(T a, T b)      { return add(a, b); }
    public T Subtract(T a, T b) { return subtract(a, b); }
    public T Multiply(T a, T b) { return multiply(a, b); }
    public T Divide(T a, T b)   { return divide(a, b); }
}

【讨论】:

    【解决方案2】:

    我不知道这是否是您的案例的最佳方法,但它对类似情况也很有用。

    这可以通过使用dynamic 关键字来完成。 dynamic 会做的是直到运行时才会进行编译时检查。

    这里有一个小示例程序来展示它是如何工作的。

    class Program
    {
        static void Main()
        {
            List<byte> bytes = new List<byte>();
            bytes.Add(2);
            bytes.Add(1);
    
            List<float> floats = new List<float>();
            floats.Add(2.5F);
            floats.Add(1F);
    
            Console.WriteLine(DoStuff(bytes));
            Console.WriteLine(DoStuff(floats));
            Console.ReadLine();
        }
    
        static dynamic DoStuff(IList items)
        {
            dynamic item0 = items[0];
            dynamic item1 = items[1];
            return item0 - item1;
        }
    
    }
    

    不幸的是,在我的快速测试中,我无法创建 IList&lt;dynamic&gt; work,但是使用非通用 IList 然后以 dynamic 的身份访问成员可以正常工作。

    【讨论】:

    • 这将是一个非常好的解决方案,但看起来 dynamic 关键字仅在 4.0 中可用。可悲的是,我被限制在3.5。呸:-(
    【解决方案3】:

    创建类来包装底层值,并让它们各自实现一个接口,实现您需要的操作。然后,使用该接口的ILists 而不是原始值。

    【讨论】:

    • @Bugmaster 对性能的影响有多大?您是否对其进行了分析,或者您是否在假设?它可能比你想象的要少很多。
    • 如果您非常关心性能,只需为每种数据类型复制该函数即可。那你可以试试我的方法,对比一下性能。