Skip to content

Instantly share code, notes, and snippets.

@admir-live
Created February 27, 2024 23:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save admir-live/63ed3a6e6b7fca101a3f5df4b053884b to your computer and use it in GitHub Desktop.
Save admir-live/63ed3a6e6b7fca101a3f5df4b053884b to your computer and use it in GitHub Desktop.
IFormattable in .NET 8
using System.Collections.Concurrent;
using System.Globalization;
var money = new Money(1234.56m, "USD");
Console.WriteLine(money.ToString("N", new CultureInfo("en-EN"))); // 1,234.56 USD
money = new Money(1234.56m, "EUR");
Console.WriteLine(money.ToString("N", new CultureInfo("de-DE"))); // 1.234,56 EUR
money = new Money(1234.56m, "JPY");
Console.WriteLine(money.ToString("N", new CultureInfo("ja-JP"))); // 1,234.56 JPY
money = new Money(1234.56m, "GBP");
Console.WriteLine(money.ToString("N", new CultureInfo("en-GB"))); // 1,234.56 GBP
public sealed class MoneyFormatter
{
private static ConcurrentDictionary<string, string> CurrencySymbolsCache { get; } = new();
public static string Format(Money money, string? format, IFormatProvider? formatProvider)
{
formatProvider ??= CultureInfo.CurrentCulture;
format = EnsureValidFormat(format);
return format.ToUpperInvariant() switch
{
"N" => FormatAsNumber(money, formatProvider),
"C" => FormatAsCurrency(money, formatProvider),
_ => throw new FormatException($"The {format} format string is not supported.")
};
}
private static string FormatAsNumber(Money money, IFormatProvider formatProvider) =>
$"{money.Amount.ToString("N2", formatProvider)} {money.CurrencyCode}";
private static string FormatAsCurrency(Money money, IFormatProvider formatProvider) =>
$"{GetCurrencySymbol(money.CurrencyCode, formatProvider)}{money.Amount.ToString("N2", formatProvider)}";
private static string EnsureValidFormat(string? format) =>
string.IsNullOrWhiteSpace(format) ? "N" : format.Trim();
private static string GetCurrencySymbol(string currencyCode, IFormatProvider formatProvider) =>
CurrencySymbolsCache.GetOrAdd(currencyCode, code => LookupCurrencySymbol(currencyCode, formatProvider));
private static string LookupCurrencySymbol(string currencyCode, IFormatProvider formatProvider)
{
if (formatProvider is CultureInfo cultureInfo && !string.IsNullOrEmpty(cultureInfo.Name))
{
var region = new RegionInfo(cultureInfo.Name);
if (region.ISOCurrencySymbol == currencyCode)
{
return region.CurrencySymbol;
}
}
return $"[{currencyCode}]";
}
}
public record Money(decimal Amount, string CurrencyCode) : IFormattable
{
private static MoneyFormatter Formatter { get; } = new();
public string ToString(string? format, IFormatProvider? formatProvider) =>
MoneyFormatter.Format(this, format, formatProvider);
}
@admir-live
Copy link
Author

Here's a breakdown of the code.

Purpose:

  • This code neatly formats money values (amounts) based on different currencies and cultural preferences.
  • It makes it easy to display amounts like 1234.56 as "$1,234.56" (USD) or "1.234,56 €" (EUR).

Key Parts:

  1. The Money Record:

    • Defines a simple way to store money data:
      • Amount: The decimal value (e.g., 1234.56).
      • CurrencyCode: The currency (e.g., "USD", "EUR", "JPY").
  2. MoneyFormatter Class:

    • Handles the actual formatting of Money.
    • Format Method: The core function that formats based on a format code:
      • "N" - Number format: "1,234.56 USD"
      • "C" - Currency format: "$1,234.56"
    • Uses a dictionary (CurrencySymbolsCache) to efficiently store currency symbols.

How it Works (Example):

var money = new Money(1234.56m, "USD");
Console.WriteLine(money.ToString("C", new CultureInfo("en-US"))); // Output: $1,234.56
  • Creation: We create a Money object representing $1234.56.
  • Formatting: We call money.ToString to format it, specifying:
    • "C" to indicate currency format.
    • "en-US" culture for US-specific symbols and styles.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment