3 ms·
Here’s version with modern C#: public record Product(string Name, decimal Price); public record OrderLine(Product Product, int Quantity); public abstra
by piskov 3y ago
Here’s version with modern C#:
public record Product(string Name, decimal Price);
public record OrderLine(Product Product, int Quantity);
public abstract record Discount
{
public record Percentage(decimal Value) : Discount();
public record FixedAmount(decimal Value) : Discount();
}
public record Order(List<OrderLine> OrderLines, Discount? Discount)
{
public decimal CalculateTotalPrice()
{
var subtotal = OrderLines.Sum(x => x.Product.Price * x.Quantity);
return subtotal - Discount switch
{
Discount.Percentage { } percentage => subtotal * percentage.Value / 100M,
Discount.FixedAmount { } amount => amount.Value,
_ => 0M
};
}
}
- CharlieDigital 3y agoI noted the same thing: the C# was unusually verbose. I thought about switching to F# a few months back, but as I started working with it, it wasn't enough of a benefit over C#.
- ctenb 3y agoTo be fair, you'd still need a private constructor in Discount, otherwise the sumtype is not closed, like it is in the F# case. And the percentage Value should be `float` :)
- sparkie 3y agoYou also want the subclasses to be `sealed`. Ideally you would want to write `abstract sealed` for `Discount` too, but C# does not allow it. CIL does support it however, and it is how F# implements sum types. There's no reason I see for C# to continue enforcing this restriction, and lifting it would allow us to write proper DUs.
- ctenb 3y agoWhile I tend to make the SUM type cases sealed, it is theoretically not required to make the sum type closed. All subtypes of a case belong by definition to that case. So any code that handles the defined cases also handles the subclasses implicitly.