Skip to content

Commit

Permalink
Changed structs to readonly structs
Browse files Browse the repository at this point in the history
  • Loading branch information
remyvd committed Mar 29, 2020
1 parent 450dd95 commit 2588910
Show file tree
Hide file tree
Showing 6 changed files with 35 additions and 27 deletions.
21 changes: 9 additions & 12 deletions src/NodaMoney/Currency.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;

[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("NodaMoney.Tests")]
[assembly: InternalsVisibleTo("NodaMoney.Tests")]
[assembly: CLSCompliant(true)]

namespace NodaMoney
Expand All @@ -19,16 +20,12 @@ namespace NodaMoney
[Serializable]
[DebuggerDisplay("{Code}")]
[TypeConverter(typeof(CurrencyTypeConverter))]
public struct Currency : IEquatable<Currency>, IXmlSerializable, ISerializable
public readonly struct Currency : IEquatable<Currency>, IXmlSerializable, ISerializable
{
/// <summary>Gets the currency sign (¤), a character used to denote the generic currency sign, when no currency sign is available.</summary>
/// <remarks>See https://en.wikipedia.org/wiki/Currency_sign_(typography). </remarks>
public const string GenericCurrencySign = "¤";

/// <summary>A singleton instance of the currencies registry.</summary>
[NonSerialized]
internal static CurrencyRegistry Registry = new CurrencyRegistry();

/// <summary>Initializes a new instance of the <see cref="Currency" /> struct.</summary>
/// <param name="code">The code.</param>
/// <param name="namespace">The namepspace.</param>
Expand Down Expand Up @@ -134,11 +131,11 @@ public static Currency CurrentCurrency

/// <summary>Gets the date when the currency is valid from.</summary>
/// <value>The from date when the currency is valid.</value>
public DateTime? ValidFrom { get; internal set; }
public DateTime? ValidFrom { get; }

/// <summary>Gets the date when the currency is valid to.</summary>
/// <value>The to date when the currency is valid.</value>
public DateTime? ValidTo { get; internal set; }
public DateTime? ValidTo { get; }

/// <summary>Gets the major currency unit.</summary>
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Member of Currency type! Implementation can change in the future.")]
Expand Down Expand Up @@ -179,7 +176,7 @@ public decimal MinorUnit
/// <exception cref="ArgumentException">The 'code' is an unknown ISO 4217 currency code.</exception>
public static Currency FromCode(string code)
{
if (!Registry.TryGet(code, out Currency currency))
if (!CurrencyRegistry.TryGet(code, out Currency currency))
throw new ArgumentException($"{code} is an unknown currency code!");

return currency;
Expand All @@ -193,7 +190,7 @@ public static Currency FromCode(string code)
/// <exception cref="ArgumentException">The 'code' in the given namespace is an unknown.</exception>
public static Currency FromCode(string code, string @namespace)
{
if (!Registry.TryGet(code, @namespace, out Currency currency))
if (!CurrencyRegistry.TryGet(code, @namespace, out Currency currency))
throw new ArgumentException($"{code} is an unknown {@namespace} currency code!");

return currency;
Expand Down Expand Up @@ -250,7 +247,7 @@ public static Currency FromRegion(string name)

/// <summary>Get all currencies.</summary>
/// <returns>An <see cref="IEnumerable{Currency}"/> of all registered currencies.</returns>
public static IEnumerable<Currency> GetAllCurrencies() => Registry.GetAllCurrencies();
public static IEnumerable<Currency> GetAllCurrencies() => CurrencyRegistry.GetAllCurrencies();

/// <summary>Returns a value indicating whether two specified instances of <see cref="Currency"/> represent the same value.</summary>
/// <param name="left">The first <see cref="Currency"/> object.</param>
Expand Down Expand Up @@ -333,7 +330,7 @@ public void ReadXml(XmlReader reader)
if (reader == null)
throw new ArgumentNullException(nameof(reader));

this = FromCode(reader["Currency"]);
Unsafe.AsRef(this) = FromCode(reader["Currency"]);
}

/// <summary>
Expand Down
4 changes: 2 additions & 2 deletions src/NodaMoney/CurrencyBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public static Currency Unregister(string code, string @namespace)
if (string.IsNullOrWhiteSpace(@namespace))
throw new ArgumentNullException(nameof(@namespace));

if (!Currency.Registry.TryRemove(code, @namespace, out Currency currency))
if (!CurrencyRegistry.TryRemove(code, @namespace, out Currency currency))
throw new ArgumentException($"code {code} specifies a currency that is not found in the namespace {@namespace}!");

return currency;
Expand All @@ -110,7 +110,7 @@ public Currency Build()
public Currency Register()
{
Currency currency = Build();
if (!Currency.Registry.TryAdd(Code, Namespace, currency))
if (!CurrencyRegistry.TryAdd(Code, Namespace, currency))
throw new InvalidOperationException("The custom currency is already registered.");

return currency;
Expand Down
21 changes: 14 additions & 7 deletions src/NodaMoney/CurrencyRegistry.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;

namespace NodaMoney
{
/// <summary>Represent the central thread-safe registry for currencies.</summary>
internal class CurrencyRegistry
internal static class CurrencyRegistry
{
/// <summary>
/// The Malagasy ariary and the Mauritanian ouguiya are technically divided into five subunits (the iraimbilanja and
Expand All @@ -23,12 +23,19 @@ internal class CurrencyRegistry
private static readonly ConcurrentDictionary<string, Currency> Currencies = new ConcurrentDictionary<string, Currency>(InitializeIsoCurrencies());
private static readonly ConcurrentDictionary<string, byte> Namespaces = new ConcurrentDictionary<string, byte>(new Dictionary<string, byte> { ["ISO-4217"] = default, ["ISO-4217-HISTORIC"] = default });

// static CurrencyRegistry()
// {
// int concurrency = Environment.ProcessorCount * 4;
// Currencies = new ConcurrentDictionary<string, Currency>(concurrency, InitializeIsoCurrencies(), StringComparer.OrdinalIgnoreCase);
// Namespaces = new ConcurrentDictionary<string, byte>(concurrency, new Dictionary<string, byte> { ["ISO-4217"] = default, ["ISO-4217-HISTORIC"] = default }, StringComparer.OrdinalIgnoreCase);
// }

/// <summary>Tries the get <see cref="Currency"/> of the given code and namespace.</summary>
/// <param name="code">A currency code, like EUR or USD.</param>
/// <param name="currency">When this method returns, contains the <see cref="Currency"/> that has the specified code, or the default value of the type if the operation failed.</param>
/// <returns><b>true</b> if <see cref="CurrencyRegistry"/> contains a <see cref="Currency"/> with the specified code; otherwise, <b>false</b>.</returns>
/// <exception cref="System.ArgumentNullException">The value of 'code' cannot be null or empty.</exception>
public bool TryGet(string code, out Currency currency)
public static bool TryGet(string code, out Currency currency)
{
if (string.IsNullOrWhiteSpace(code))
throw new ArgumentNullException(nameof(code));
Expand All @@ -53,7 +60,7 @@ public bool TryGet(string code, out Currency currency)
/// <param name="currency">When this method returns, contains the <see cref="Currency"/> that has the specified code and namespace, or the default value of the type if the operation failed.</param>
/// <returns><b>true</b> if <see cref="CurrencyRegistry"/> contains a <see cref="Currency"/> with the specified code; otherwise, <b>false</b>.</returns>
/// <exception cref="System.ArgumentNullException">The value of 'code' or 'namespace' cannot be null or empty.</exception>
public bool TryGet(string code, string @namespace, out Currency currency)
public static bool TryGet(string code, string @namespace, out Currency currency)
{
if (string.IsNullOrWhiteSpace(code))
throw new ArgumentNullException(nameof(code));
Expand All @@ -70,7 +77,7 @@ public bool TryGet(string code, string @namespace, out Currency currency)
/// <param name="currency">When this method returns, contains the <see cref="Currency"/> that has the specified code and namespace, or the default value of the type if the operation failed.</param>
/// <returns><b>true</b> if the <see cref="Currency"/> with the specified code is added; otherwise, <b>false</b>.</returns>
/// <exception cref="System.ArgumentNullException">The value of 'code' or 'namespace' cannot be null or empty.</exception>
public bool TryAdd(string code, string @namespace, Currency currency)
public static bool TryAdd(string code, string @namespace, Currency currency)
{
if (string.IsNullOrWhiteSpace(code))
throw new ArgumentNullException(nameof(code));
Expand All @@ -88,7 +95,7 @@ public bool TryAdd(string code, string @namespace, Currency currency)
/// <param name="currency">When this method returns, contains the <see cref="Currency"/> that has the specified code and namespace, or the default value of the type if the operation failed.</param>
/// <returns><b>true</b> if the <see cref="Currency"/> with the specified code is removed; otherwise, <b>false</b>.</returns>
/// <exception cref="System.ArgumentNullException">The value of 'code' or 'namespace' cannot be null or empty.</exception>
public bool TryRemove(string code, string @namespace, out Currency currency)
public static bool TryRemove(string code, string @namespace, out Currency currency)
{
if (string.IsNullOrWhiteSpace(code))
throw new ArgumentNullException(nameof(code));
Expand All @@ -101,7 +108,7 @@ public bool TryRemove(string code, string @namespace, out Currency currency)

/// <summary>Get all registered currencies.</summary>
/// <returns>An <see cref="IEnumerable{Currency}"/> of all registered currencies.</returns>
public IEnumerable<Currency> GetAllCurrencies()
public static IEnumerable<Currency> GetAllCurrencies()
{
return Currencies.Values.AsEnumerable();
}
Expand Down
7 changes: 5 additions & 2 deletions src/NodaMoney/Money.Serializable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using System.Runtime.CompilerServices;

namespace NodaMoney
{
Expand Down Expand Up @@ -79,8 +80,10 @@ public void ReadXml(XmlReader reader)
if (reader.MoveToContent() != XmlNodeType.Element)
throw new SerializationException("Couldn't find content element with name Money!");

Amount = decimal.Parse(reader["Amount"], CultureInfo.InvariantCulture);
Currency = (Currency)TypeDescriptor.GetConverter(typeof(Currency)).ConvertFromString(reader["Currency"]);
var amount = decimal.Parse(reader["Amount"], CultureInfo.InvariantCulture);
var currency = (Currency)TypeDescriptor.GetConverter(typeof(Currency)).ConvertFromString(reader["Currency"]);

Unsafe.AsRef(this) = new Money(amount, currency);
}

/// <summary>Converts an object into its XML representation.</summary>
Expand Down
6 changes: 3 additions & 3 deletions src/NodaMoney/Money.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace NodaMoney
/// and ensure that two different currencies cannot be added or subtracted to each other.
/// </remarks>
[StructLayout(LayoutKind.Sequential)]
public partial struct Money : IEquatable<Money>
public readonly partial struct Money : IEquatable<Money>
{
/// <summary>Initializes a new instance of the <see cref="Money"/> struct, based on the current culture.</summary>
/// <param name="amount">The Amount of money as <see langword="decimal"/>.</param>
Expand Down Expand Up @@ -220,10 +220,10 @@ public Money(ulong amount, Currency currency)
}

/// <summary>Gets the amount of money.</summary>
public decimal Amount { get; private set; }
public decimal Amount { get; }

/// <summary>Gets the <see cref="Currency"/> of the money.</summary>
public Currency Currency { get; private set; }
public Currency Currency { get; }

/// <summary>Returns a value indicating whether two instances of <see cref="Money"/> are equal.</summary>
/// <param name="left">A <see cref="Money"/> object on the left side.</param>
Expand Down
3 changes: 2 additions & 1 deletion src/NodaMoney/NodaMoney.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<AssemblyTitle>NodaMoney</AssemblyTitle>
Expand Down Expand Up @@ -32,6 +32,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.7.1" />
<PackageReference Include="Text.Analyzers" Version="2.6.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
Expand Down

0 comments on commit 2588910

Please sign in to comment.