J'ai d'abord suivi ce tutoriel pour créer mon objet Money: https://www.codeproject.com / articles / 837791 / money-pattern

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

J'obtiens une exception de compilation sur y.Amount:

Impossible de convertir implicitement le type 'Money' en 'long?' Impossible de convertir l'expression lambda en type de délégué prévu car certains des types de retour dans le bloc ne sont pas implicitement convertibles en type de retour délégué

Qu'est-ce que je fais mal?

Voici ma classe 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
4
Bastien Vandamme 26 janv. 2017 à 05:51

4 réponses

Meilleure réponse

Vraisemblablement InvoiceDetails est une collection de classes qui contiennent une propriété public Money Amount, par exemple:

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

Dans ce cas, vous pouvez utiliser {{X0} } pour faire la somme:

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

Pour vous débarrasser de l'expression légèrement moche new Money(0, InvoiceDetails.First().Amount.Currency), vous pourriez vouloir introduire un singleton spécial Money.Empty qui ne contient pas d'argent et peut être ajouté à n'importe quel type d'argent. Ou modifiez les opérateurs statiques pour accepter une valeur null pour Money et faites:

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

Alternativement, l'introduction d'un intermédiaire Select() peut rendre l'expression plus propre:

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

La raison pour laquelle {{ X0}} ne fonctionne pas, c'est qu'il est défini pour un ensemble fixe de types arithmétiques énumérables. Il n'y a pas de Sum() pour les types arbitraires pour lesquels des surcharges d'opérateurs arithmétiques ont été introduites, car il n'y a pas d'interface commune ou d'inférence de type pour un tel scénario. (Voir Y a-t-il une contrainte qui restreint ma méthode générique aux types numériques?, à laquelle la réponse est, "non".) Bien sûr, vous pourrait ajouter votre propre version de Enumerable.Sum() qui prend en charge les types qui fournissent leur propre arithmétique, voir par exemple cette réponse pour un point de départ.

5
Community 23 mai 2017 à 12:00

Vous pouvez ajouter un opérateur implicite qui permettrait à l'objet d'être converti en un type pouvant être additionné.

public static implicit operator decimal(Money money) {
    return money != null ? money.Amount : 0;
}
0
Nkosi 26 janv. 2017 à 04:03

J'ai récemment résolu un problème similaire avec les deux méthodes d'extension suivantes.

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);
1
Michael B 20 avril 2020 à 19:23

Dans la même veine que la réponse de @ dbc. Vous devez créer votre propre exemple concret de la méthode Sum. Cela est dû au fait que operator est statique et ne peut pas être interfacé.

J'emprunterais en fait une voie légèrement différente de celle de dbc car

  1. L'utilisation de Enumerable.First() provoque plusieurs itérations de IEnumerable
  2. Enumerable.First() échouera s'il n'y a pas d'éléments, mais donnera une mauvaise exception.
  3. La réutilisation du code rend les choses plus claires.

Au lieu de cela, je préfère.

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);
        }
    }
}
1
Aron 26 janv. 2017 à 05:48