算术运算,例如Add for a Generic List <t>

时间:2017-08-03 18:47:42

标签: c# linq generics lambda expression

T a, T b这样的两个元素添加添加很简单,Mark使用表达式树here提供了一个很好的解决方案,它可以转换为以下内容并且易于使用:

static T Add<T>(T a, T b)
{
    // Declare Parameter Expressions
    ParameterExpression paramA = Expression.Parameter(typeof(T), "valueA"),
        paramB = Expression.Parameter(typeof(T), "valueB");

    // add the parameters together
    BinaryExpression body = Expression.Add(paramA, paramB);

    // Compile it
    Func<T, T, T> add = Expression.Lambda<Func<T, T, T>>(body, paramA, paramB).Compile();

    // Call it
    return add(a, b);
}

我所遇到的挑战是List<T>的集合,其中所有元素都必须添加,如上所示。我尝试过以上相同的方式,但是它不起作用:

static T AddAll<T>(List<T> list)
{
    var parameterExpressionList = list.Select((x,i) => (Expression)Expression.Parameter(typeof(T), "value"+i));

    var body =  parameterExpressionList
                                  .Skip(1)
                                  .Aggregate(parameterExpressionList.First(), 
                                   (paramA, paramB) => Expression.Add(paramA, paramB));

    // Compile it
    Func<List<T>, T> addAll = Expression.Lambda<Func<List<T>, T>>(body, parameterExpressionList.Cast<ParameterExpression>()).Compile();

    return addAll(list);
}

我得到的运行时错误是:为lambda声明提供的参数数量不正确。任何指针,如何实现,请注意,我不需要一个解决方案,我累积从实际列表中选择两个元素并调用Add<T>(T a, T b),因为这将导致多次编译表达式树,没有效率,因为我会&gt; 100 K数据点,任何使我的代码工作的建议都会很棒,我不确定它出错的地方。

6 个答案:

答案 0 :(得分:1)

由于您已经创建了一个泛型函数,只需在列表中使用它(我添加了一个可选的Adder方法来处理非标准类):

static T AddAll<T>(IEnumerable<T> src, Func<T, T, T> adder = null) {
    // Declare Parameter Expressions
    ParameterExpression paramA = Expression.Parameter(typeof(T), "valueA"),
        paramB = Expression.Parameter(typeof(T), "valueB");

    // add the parameters together
    BinaryExpression body;
    if (adder == null)
        body = Expression.Add(paramA, paramB);
    else
        body = Expression.Add(paramA, paramB, adder.GetMethodInfo());

    // Compile it
    Func<T, T, T> add = Expression.Lambda<Func<T, T, T>>(body, paramA, paramB).Compile();

    // Call it
    return src.Aggregate(default(T), (ans, n) => add(ans, n));
}

您可以使用Adder参数来处理strings

等内容
var ans = AddAll(new[] { "a", "b", "c" }, String.Concat);

由于我们在编译时知道T的类型,我们只需调用Sum

static T AddAll2<T>(IEnumerable<T> src) {
    var paramA = Expression.Parameter(typeof(IEnumerable<T>), "valueA");

    var method = typeof(Enumerable).GetMethod("Sum", new[] { typeof(IEnumerable<T>) });
    if (method != null) {
        // Create lambda body
        var body = Expression.Call(method, paramA);

        // Compile it
        Func<IEnumerable<T>, T> sum = Expression.Lambda<Func<IEnumerable<T>, T>>(body, paramA).Compile();

        // Call it
        return sum(src);
    }
    else
        return default(T);
}

当然,如果你要打电话给Sum,你不需要一个lambda:

static T AddAll3<T>(IEnumerable<T> src) {
    var method = typeof(Enumerable).GetMethod("Sum", new[] { typeof(IEnumerable<T>) });
    if (method != null) {
        // Call it
        return (T)method.Invoke(null, new[] { src });
    }
    else
        return default(T);
}

答案 1 :(得分:1)

只是尝试从列表中获取每个项目,然后将它们累积到结果中。

    static T AddAll<T>(List<T> list)
    {
        if (list.Count == 0)
        {
            // It's additional small case
            return default(T);
        }

        var listParam = Expression.Parameter(typeof(List<T>));

        var propInfo = typeof(List<T>).GetProperty("Item");
        var indexes = list.Select((x, i) => Expression.MakeIndex(listParam, propInfo, new[] { Expression.Constant(i) }));

        Expression sum = indexes.First();
        foreach (var item in indexes.Skip(1))
        {
            sum = Expression.Add(sum, item);
        }

        var lambda = Expression.Lambda<Func<List<T>, T>>(sum, listParam).Compile();
        return lambda(list);
    }

答案 2 :(得分:1)

您可以直接将列表作为参数传递,只需通过索引创建总和:

static T AddAll<T>(List<T> list)
{
    if (list.Count == 0) return default(T);
    if (list.Count == 1) return list[0];

    var indexerProperty = typeof(List<T>).GetProperty("Item");

    var p = Expression.Parameter(typeof(List<T>));
    var exp = Expression.Add(
        Expression.MakeIndex(p, indexerProperty, new [] { Expression.Constant(0) }),
        Expression.MakeIndex(p, indexerProperty, new [] { Expression.Constant(1) }));
    for (var i = 2; i < list.Count; i++)
    {
        exp = Expression.Add(
            exp,
            Expression.MakeIndex(p, indexerProperty, new [] { Expression.Constant(i) }));
    }

    var lambda = Expression.Lambda<Func<List<T>, T>>(exp, p).Compile();
    return lambda(list);
}

答案 3 :(得分:1)

将所有适用的Enumerable.Sum重载存储在字典中:

// all methods with signature public static T Enumerable.Sum(IEnumerable<T>) by element type
private static readonly Dictionary<Type, MethodInfo> _sumMethodsByElementType = typeof(Enumerable)
    .GetMethods(BindingFlags.Public | BindingFlags.Static)
    .Where(m => m.Name == "Sum" && !m.IsGenericMethod)
    .Select(m => new { Method = m, Parameters = m.GetParameters() })
    .Where(mp => mp.Parameters.Length == 1)
    .Select(mp => new { mp.Method, mp.Parameters[0].ParameterType })
    .Where(mp => mp.ParameterType.IsGenericType && mp.ParameterType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
    .Select(mp => new { mp.Method, ElementType = mp.ParameterType.GetGenericArguments()[0] })
    .Where(me => me.Method.ReturnType == me.ElementType)
    .ToDictionary(mp => mp.ElementType, mp => mp.Method);

从通用AddAll(或我更喜欢称之为Sum)方法中调用相应的方法:

public static T Sum<T>(IEnumerable<T> summands)
{
    MethodInfo sumMethod;
    if (!_sumMethodsByElementType.TryGetValue(typeof(T), out sumMethod)) throw new InvalidOperationException($"Cannot sum elements of type {typeof(T)}.");

    return (T)sumMethod.Invoke(null, new object[] { summands });
}

测试:

Console.WriteLine(Sum(new[] { 1, 2, 3 }));
Console.WriteLine(Sum(new[] { 1, 2, 3, default(int?) }));
Console.WriteLine(Sum(new[] { 1.1, 2.2, 3.3 }));
Console.WriteLine(Sum(new[] { 1.1, 2.2, 3.3, default(double?) }));

try { Console.WriteLine(Sum(new[] { 'a', 'b', 'c' })); }
catch (InvalidOperationException ex) { Console.WriteLine(ex.Message); }

输出:

6
6
6.6
6.6
Cannot sum elements of type System.Char.

答案 4 :(得分:1)

如果您只是对操作本身感兴趣,那么您不一定需要用表达式解决问题的每个部分

这是一个通过Singleton类型使用Lazy<>进行默认添加类型T(而不是静态方法)的实现

如果真的需要表达式(例如,在EF场景中),LinqExpression表达式可能会被重用,但AddAll没有等效表达式} operation ...虽然它可以扩展为支持AddAll通用表达式

public abstract class Addition<T>
{
    private readonly Lazy<Expression<Func<T, T, T>>> _lazyExpression;
    private readonly Lazy<Func<T, T, T>> _lazyFunc;

    public Func<T, T, T> Execute
    {
        get { return _lazyFunc.Value; }
    }

    public Expression<Func<T, T, T>> LinqExpression
    {
        get { return _lazyExpression.Value; }
    }

    protected Addition()
    {
        _lazyExpression = new Lazy<Expression<Func<T, T, T>>>(InitializeExpression);
        _lazyFunc = new Lazy<Func<T, T, T>>(() => LinqExpression.Compile());
    }

    protected abstract Expression<Func<T, T, T>> InitializeExpression();
}

public sealed class DefaultAddition<T> : Addition<T>
{
    private static readonly Lazy<DefaultAddition<T>> _lazyInstance = new Lazy<DefaultAddition<T>>(() => new DefaultAddition<T>());

    public static DefaultAddition<T> Instance
    {
        get {return _lazyInstance.Value; }
    }

    // Private constructor, you only get an instance via the Instance static property
    private DefaultAddition()
    {
    }

    protected override Expression<Func<T, T, T>> InitializeExpression()
    {
        var paramX = Expression.Parameter(typeof(T), "x");
        var paramY = Expression.Parameter(typeof(T), "y");
        var body = Expression.Add(paramX, paramY);
        return Expression.Lambda<Func<T, T, T>>(body, paramX, paramY);
    }
}

public static class Operations
{
    public static T Add<T>(T x, T y)
    {
        return DefaultAddition<T>.Instance.Execute(x, y);
    }

    public static T AddAll<T>(IEnumerable<T> enumerable)
    {
        var itemAdd = DefaultAddition<T>.Instance.Execute;
        return enumerable.Aggregate(default(T), (result, item) => itemAdd(result, item));

        // This might be more efficient than Aggregate, but I didn't benchmark it

        /*
        var result = default(T);
        foreach (var item in enumerable)
        {
            result = itemAdd(result, item);
        }
        return result;
        */
    }
}

<强>用法:

// Can mix double with int :)
var doubleAdd = Operations.Add(4.5, 3);

// Can mix decimal with int :)
var listAdd = Operations.AddAll(new[] {3, 6.7m, 0.3m});

// Even empty enumerables
var shortAdd = Operations.AddAll(Enumerable.Empty<short>());

// This will not work for byte. System.Byte should be casted to System.Int32
// Throws "InvalidOperationException: The binary operator Add is not defined for the types 'System.Byte' and 'System.Byte'."
var byteAdd = Operations.AddAll(new byte[] {1, 2, 3});

答案 5 :(得分:0)

如果您的T类型为int,long,double等值,那么您可以这样做:

//add
//using System.Linq;

var items = new List<int>();
items.Add(1);
items.Add(5);
items.Add(10);

var sum = items.Sum();