为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数据点,任何使我的代码工作的建议都会很棒,我不确定它出错的地方。
答案 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();