【问题标题】:How to sum a custom object with LinQ?如何用 LinQ 对自定义对象求和?
【发布时间】:2017-06-11 10:28:38
【问题描述】:

首先我按照本教程创建了我的 Money 对象:https://www.codeproject.com/articles/837791/money-pattern

Money totalItems = _invoice.InvoiceDetails
    .Sum(y => y.Amount); // Amount is of type Money

我在y.Amount 上遇到编译异常:

不能隐式转换类型 '钱' 到' 多头?不能 将 lambda 表达式转换为预期的委托类型,因为一些 块中的返回类型不能隐式转换为 委托返回类型

我做错了什么?

这是我的 Money 课程:

public class Money
{
    public decimal Amount { get; private set; }
    public CurrencyCode Currency { get; private set; }

    #region Constructors
    public Money() { }
    public Money(Money amount)
    {
        this.Amount = amount.Amount;
        this.Currency = amount.Currency;
    }
    public Money(decimal amount, CurrencyCode currencyCode)
    {
        this.Amount = amount;
        this.Currency = currencyCode;
    }
    public Money(int amount, CurrencyCode currency)
        : this(Convert.ToDecimal(amount), currency)
    {
    }
    public Money(double amount, CurrencyCode currency)
        : this(Convert.ToDecimal(amount), currency)
    {
    }
    #endregion

    #region Comprasion operators
    public static bool operator ==(Money var1, Money var2)
    {
        if ((object)var1 == null || (object)var2 == null)
            return false;

        if (var1.Currency != var2.Currency) return false;
        return var1.Amount == var2.Amount;
    }


    public static bool operator !=(Money var1, Money var2)
    {
        return !(var1 == var2);
    }

    public static bool operator >(Money var1, Money var2)
    {
        if (var1.Currency != var2.Currency)
            throw new InvalidOperationException("Comprasion between different currencies is not allowed.");

        return var1.Amount > var2.Amount;
    }

    public static bool operator <(Money var1, Money var2)
    {
        if (var1 == var2) return false;

        return !(var1 > var2);
    }

    public static bool operator <=(Money var1, Money var2)
    {
        if (var1 < var2 || var1 == var2) return true;

        return false;
    }

    public static bool operator >=(Money var1, Money var2)
    {
        if (var1 > var2 || var1 == var2) return true;

        return false;
    }
    #endregion

    #region Ariphmetical operations
    public static Money operator +(Money var1, Money var2)
    {
        if (var1.Currency != var2.Currency)
        {
            throw new InvalidCastException("Calculation is using different currencies!");
        }

        return new Money(var1.Amount + var2.Amount, var1.Currency);
    }

    public static Money operator -(Money var1, Money var2)
    {
        if (var1.Currency != var2.Currency)
        {
            throw new InvalidCastException("Calculation is using different currencies!");
        }

        return new Money(var1.Amount - var2.Amount, var1.Currency);
    }

    public static Money operator *(Money var1, Money var2)
    {
        if (var1.Currency != var2.Currency)
        {
            throw new InvalidCastException("Calculation is using different currencies!");
        }

        return new Money(var1.Amount * var2.Amount, var1.Currency);
    }

    public static Money operator /(Money var1, Money var2)
    {
        if (var1.Currency != var2.Currency)
        {
            throw new InvalidCastException("Calculation is using different currencies!");
        }

        return new Money(var1.Amount / var2.Amount, var1.Currency);
    }

    public static Money operator *(decimal var1, Money var2)
    {
        return new Money(var1 * var2.Amount, var2.Currency);
    }

    public static Money operator *(Money var1, decimal var2)
    {
        return new Money(var1.Amount * var2, var1.Currency);
    }

    public static Money operator /(decimal var1, Money var2)
    {
        return new Money(var1 / var2.Amount, var2.Currency);
    }

    public static Money operator /(Money var1, decimal var2)
    {
        return new Money(var1.Amount / var2, var1.Currency);
    }

    public static Money operator *(int var1, Money var2)
    {
        return new Money(var1 * var2.Amount, var2.Currency);
    }

    public static Money operator *(Money var1, int var2)
    {
        return new Money(var1.Amount * var2, var1.Currency);
    }

    public static Money operator /(int var1, Money var2)
    {
        return new Money(var1 / var2.Amount, var2.Currency);
    }

    public static Money operator /(Money var1, int var2)
    {
        return new Money(var1.Amount / var2, var1.Currency);
    }

    public static Money operator *(long var1, Money var2)
    {
        return new Money(var1 * var2.Amount, var2.Currency);
    }

    public static Money operator *(Money var1, long var2)
    {
        return new Money(var1.Amount * var2, var1.Currency);
    }

    public static Money operator /(long var1, Money var2)
    {
        return new Money(var1 / var2.Amount, var2.Currency);
    }

    public static Money operator /(Money var1, long var2)
    {
        return new Money(var1.Amount / var2, var1.Currency);
    }
    #endregion

    public override bool Equals(object obj)
    {
        if (obj == null) return false;

        Money money = obj as Money;
        return (this.Amount == money.Amount && this.Currency == money.Currency);
    }

    public bool Equals(Money money)
    {
        if ((object)money == null) return false;

        return (this.Amount == money.Amount && this.Currency == money.Currency);
    }

    public override int GetHashCode()
    {
        return base.GetHashCode();
    }

    public override string ToString()
    {
        return this.Amount.ToString();
    }
    #endregion

【问题讨论】:

  • 试试 y.Amount.Amount?
  • @shole 真的吗?当然可以,但我失去了 Money 课程的所有好处。
  • 好的,我错过了你问题的全部要点,你试图覆盖操作员并让 LINQ 直接对 Money(y.Amount is Money...这很混乱!!) 进行求和。 ...也许这篇文章可以帮助你一点? stackoverflow.com/questions/17717858/linq-sum-on-objects
  • 我怀疑您能否成功指导 Linq 提供者如何将此类表达式转换为 SQL。虽然像 Money 这样的自定义类是完全可以接受的,但它们应该出现在 BAL 的上一层,而不是 DAL。

标签: c# linq


【解决方案1】:

大概InvoiceDetails 是包含public Money Amount 属性的类的集合,例如:

public class InvoiceDetail
{
    public Money Amount { get; set; }
}

在这种情况下,您可以使用Enumerable.Aggregate() 进行求和:

var sum = InvoiceDetails.Aggregate(new Money(0, InvoiceDetails.First().Amount.Currency), (s, d) => s + d.Amount);

要摆脱略显丑陋的new Money(0, InvoiceDetails.First().Amount.Currency) 表达式,您可能需要引入一个特殊的单例Money.Empty,它不包含钱,可以添加到任何类型的钱中。或者修改静态运算符以接受 Moneynull 值并执行以下操作:

var sum = InvoiceDetails.Aggregate((Money)null, (s, d) => s + d.Amount);

另外,引入中间的Select() 可能会使表达式更简洁:

var sum = InvoiceDetails.Select(d => d.Amount).Aggregate((s, a) => s + a);

Enumerable.Sum() 不起作用的原因是它是为一组固定的可枚举算术类型定义的。对于已引入算术运算符重载的任意类型,没有Sum(),因为在这种情况下没有通用接口或类型推断。 (请参阅Is there a constraint that restricts my generic method to numeric types?,答案是"no"。)当然,您可以添加自己的Enumerable.Sum() 版本,它支持提供自己算术的类型,请参阅例如this answer 一个开始的地方。

【讨论】:

    【解决方案2】:

    与@dbc 的回答类似。您需要创建自己的 Sum 方法的具体示例。这是因为operator 是静态的,不能接口。

    我实际上会走一条与 dbc 略有不同的路线,因为

    1. Enumerable.First() 的使用导致IEnumerable 的多次迭代
    2. Enumerable.First() 在没有元素时会失败,但会给出一个糟糕的异常。
    3. 代码重用让事情变得更清晰。

    我更喜欢。

    namespace System.Linq //Using System.Linq namespace makes it easier to use
    {
        public static class MoneyEnumerable
        {
            public static Money Sum(this IEnumerable<Money> monies)
            {
                return monies.Aggregate((left, right) => left + right);
            }
        }
    }
    

    【讨论】:

      【解决方案3】:

      我最近用以下两种扩展方法解决了类似的问题。

      public static Money Sum<T>(this IEnumerable<T> source, Func<T, Money> selector)
          => Sum(source.Select(selector));
      
      public static Money Sum(this IEnumerable<Money> money)
          => money.Aggregate((left, right) => left + right);
      

      【讨论】:

        【解决方案4】:

        您可以添加一个隐式运算符,该运算符允许将对象转换为可以求和的类型。

        public static implicit operator decimal(Money money) {
            return money != null ? money.Amount : 0;
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-10-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多