在C#中编写通用算术

4
我有一组数字,并编写了一个方法对这些数字执行一些计算,总共大约是一页代码。该方法对这些数字执行一些算术和比较。
我的问题是,在某种情况下,列表是一个IList<byte>,而在另一种情况下,它是一个IList<float>。两种情况下的算法完全相同(是的,我知道会出现诸如溢出错误和精度损失之类的问题,但在我的情况下它能工作)。如何编写一个可以处理这两个列表的方法?我不能编写像void DoStuff<T>(IList<T> numbers)这样的东西,因为没有通用的算术运算符(+ - * /)。
一种解决方案是将所有内容都存储为float,但我想避免这种情况。列表非常长,因此将floats而不是bytes存储将花费太多内存。我也可以做一些像DoStuffFloat(byteList.Select(b => (float)b))这样的事情,但如果可以避免,我也不想付出性能代价。
除了复制整个方法并将“float”替换为“byte”(或反之亦然),是否有一些合适的解决方案?
编辑:我应该提到我在这个项目中使用的是.NET 3.5。

你能发一下你的代码吗?可以吗? - DarthVader
我们需要谈论多少列表项才会占用太多内存? - Sam Axe
将它们全部存储为整数,而不是浮点数。将浮点数放大,以便可以对它们进行整数运算,然后在收到结果后缩小比例。 - Sam Axe
你应该看一下这篇文章……其中一个链接包含一个实用类,可以做你想要的事情。https://dev59.com/V3M_5IYBdhLWcg3wslbs - alistair
我想知道是否有人尝试过IConvertible及其相关的包装函数在System.Convert中?(我不会对性能做任何声明,只是想完成这件事。) - rwong
显示剩余3条评论
3个回答

6
你可以创建一个通用接口,其中包含你想要支持的操作,创建一个通用工厂来创建支持的类型的实例来执行操作,并使用它。
例如:
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); }
}

5

我不知道这是否是您情况下最好的方法,但对于类似的情况也很有用。

可以使用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<dynamic>正常工作,但是使用非泛型的IList然后将成员视为dynamic来访问可以正常工作。


这将是一个非常好的解决方案,但看起来“dynamic”关键字仅在4.0中可用。遗憾的是,我只能使用3.5版本。唉 :-( - Bugmaster

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

@Bugmaster 这会影响性能有多大?你进行了性能分析吗,还是仅凭假设?实际影响可能比你想象中的小很多。 - Scott Chamberlain
如果你非常关注性能问题,那么就为每种数据类型复制一份函数。这样你就可以按照我的方式尝试并比较性能了。 - djs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接