Для ряда сущностей имеет смысл их представление в скалярном виде. Например, деньги состоят из количества и валюты, но при работе с ними зачастую нам нужно просто числовое представление, которое можно, в первую очередь, складывать с другими числами.
Допустим, у нас есть класс Money
со свойствами Amount
(количество) и Currency
(валюта), в которое реализовано неявное преобразование к decimal
и int
. Также перегружен оператор +
для сложения Money
с Money
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class Money { public decimal Amount { get; } public Currencies Currency { get; } ... public static implicit operator decimal(Money m) => m.Amount; public static implicit operator int(Money m) => (int)m.Amount; public static Money operator +(Money m1, Money m2) { var conv = m2.ConvertTo(m1.Currency); return new Money(m1.Amount + conv.Amount, m1.Currency); } ... } |
Это позволяет безо всяких приведений типов и выковыривания Amount
складывать Money
не только с Money
, но и с decimal
и int
. Результатом таких операций будет число decimal
или int
соответственно.
1 2 3 |
var m = new Money(100, Currencies.RUR); var x = 200; var sum = m + x; // (int)300 |
Кроме арифметических операций можно передавать Money
в качестве параметра везде, где требуется decimal
или int
.
1 2 3 4 5 6 7 8 |
private double TotalSum(double partOne, double partTwo) { return partOne + partTwo; } private void Test() { var m = new Money(100, Currency.USD); var total = TotalSum(m, 1000); // (int)1100 } |
Однако будьте внимательны и имейте в виду, что при использовании нескольких Money
таким способом их валюты будут игнорироваться, то есть такие операции следует делать только между экземпляром Money
и числами (или проводить операции отдельно над Money
и отдельно над смешанными объектами).
Говоря языком математики, операция сложения разнородных величин в данном случае теряет коммутативность обычной операции сложения чисел. То есть, от перемены мест слагаемых сумма в данном случае легко может поменяться.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var m1 = new Money(100, Currencies.RUR); var m2 = new Money(200, Currencies.USD); var sum1 = m1 + m2 + 1000; // переведет $200 (m2) в рубли (допустим, по курсу 60 получим 12000 р), сложит их со 100 рублями, после чего прибавит к ним 1000 // результат: 13100 var sum2 = 1000 + m1 + m2; // сложит все в виде чисел // результат: 1300 var sum3 = 1000 + (m1 + m2); // результат: 13100 |
Лучше вообще не складывать более двух сущностей сразу, чтобы не получить непредсказуемых результатов. Ну или используйте скобки, чтобы задать явный порядок выполнения операций.
Полный код примера (Github):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
using System; using System.Linq; namespace Money { public class Program { public static void Main() { // test 1 var m = new Money(100, Currencies.RUR); var x = 200; Console.WriteLine($"{m} + {x} = {m + x}"); // 300 //test 2 m = new Money(100, Currencies.USD); x = 1000; Console.WriteLine($"{m} + {x} = {Sum(m, x)}"); // 1100 // test 3 var m1 = new Money(100, Currencies.RUR); var m2 = new Money(200, Currencies.USD); x = 1000; Console.WriteLine($"{m1} + {m2} + {x} = {m1 + m2 + x}"); // переведет $200 (m2) в рубли (допустим, по курсу 60 получим 12000 р), сложит их со 100 рублями, после чего прибавит к ним 1000 // результат: 13100 Console.WriteLine($"{x} + {m1} + {m2} = {x + m1 + m2}"); // сложит все в виде чисел // результат: 1300 Console.WriteLine($"{x} + ({m1} + {m2}) = {x + (m1 + m2)}"); // результат: 13100 Console.ReadLine(); } private static decimal Sum(params decimal[] x) => x.Sum(); } public enum Currencies { RUR, USD, EUR } public class Money { public decimal Amount { get; } public Currencies Currency { get; } public Money(decimal amount, Currencies currency) { Amount = amount; Currency = currency; } public Money ConvertTo(Currencies newCurrency) { var newAmount = (Currency == newCurrency) ? Amount : Amount.Convert(Currency, newCurrency); return new Money(newAmount, newCurrency); } public static implicit operator decimal(Money m) => m.Amount; public static implicit operator int(Money m) => (int)m.Amount; public static Money operator +(Money m1, Money m2) { var conv = m2.ConvertTo(m1.Currency); return new Money(m1.Amount + conv.Amount, m1.Currency); } public override string ToString() => $"{Amount} {Currency}"; } public static class Extensions { public static decimal Convert(this decimal amount, Currencies from, Currencies to) => (decimal)((double)amount * from.GetCourseTo(to)); public static double GetCourseTo(this Currencies from, Currencies to) { if (from == to) return 1; // placeholder if (from == Currencies.USD && to == Currencies.RUR) { return 60; } throw new Exception($"No course set for conversion from {from} to {to}."); } } } |
Результатом выполнения этой программы будет следующий текст:
1 2 3 4 5 |
100 RUR + 200 = 300 100 USD + 1000 = 1100 100 RUR + 200 USD + 1000 = 13100 1000 + 100 RUR + 200 USD = 1300 1000 + (100 RUR + 200 USD) = 13100 |