diff --git a/.github/workflows/net48-compatibility.yml b/.github/workflows/net48-compatibility.yml index 959e57ddc2..6734ed22da 100644 --- a/.github/workflows/net48-compatibility.yml +++ b/.github/workflows/net48-compatibility.yml @@ -52,6 +52,8 @@ jobs: run: | $testProjects = @( 'UnitsNet.Tests/UnitsNet.Tests.csproj', + 'UnitsNet.GlobalSetup.DefaultFirst.Tests/UnitsNet.GlobalSetup.DefaultFirst.Tests.csproj', + 'UnitsNet.GlobalSetup.Tests/UnitsNet.GlobalSetup.Tests.csproj', 'UnitsNet.NumberExtensions.Tests/UnitsNet.NumberExtensions.Tests.csproj', 'UnitsNet.NumberExtensions.CS14.Tests/UnitsNet.NumberExtensions.CS14.Tests.csproj', 'UnitsNet.Serialization.JsonNet.Tests/UnitsNet.Serialization.JsonNet.Tests.csproj' diff --git a/Build/build-functions.psm1 b/Build/build-functions.psm1 index 88cd3b3e7f..fefdcfa372 100644 --- a/Build/build-functions.psm1 +++ b/Build/build-functions.psm1 @@ -40,6 +40,8 @@ function Start-Tests { $projectPaths = @( "UnitsNet.Tests/UnitsNet.Tests.csproj", + "UnitsNet.GlobalSetup.DefaultFirst.Tests/UnitsNet.GlobalSetup.DefaultFirst.Tests.csproj", + "UnitsNet.GlobalSetup.Tests/UnitsNet.GlobalSetup.Tests.csproj", "UnitsNet.NumberExtensions.Tests/UnitsNet.NumberExtensions.Tests.csproj", "UnitsNet.NumberExtensions.CS14.Tests/UnitsNet.NumberExtensions.CS14.Tests.csproj", "UnitsNet.Serialization.JsonNet.Tests/UnitsNet.Serialization.JsonNet.Tests.csproj" diff --git a/UnitsNet.GlobalSetup.DefaultFirst.Tests/UnitsNet.GlobalSetup.DefaultFirst.Tests.csproj b/UnitsNet.GlobalSetup.DefaultFirst.Tests/UnitsNet.GlobalSetup.DefaultFirst.Tests.csproj new file mode 100644 index 0000000000..d4b2b5d35d --- /dev/null +++ b/UnitsNet.GlobalSetup.DefaultFirst.Tests/UnitsNet.GlobalSetup.DefaultFirst.Tests.csproj @@ -0,0 +1,32 @@ + + + + net10.0 + $(TargetFrameworks);net48 + latest + enable + true + enable + xunit + + + + ../UnitsNet.snk + false + true + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/UnitsNet.GlobalSetup.DefaultFirst.Tests/UnitsNetSetupDefaultFirstTests.cs b/UnitsNet.GlobalSetup.DefaultFirst.Tests/UnitsNetSetupDefaultFirstTests.cs new file mode 100644 index 0000000000..252f55db61 --- /dev/null +++ b/UnitsNet.GlobalSetup.DefaultFirst.Tests/UnitsNetSetupDefaultFirstTests.cs @@ -0,0 +1,27 @@ +// Licensed under MIT No Attribution, see LICENSE file at the root. +// Copyright 2013 Andreas Gullberg Larsen (andreas.larsen84@gmail.com). Maintained at https://github.com/angularsen/UnitsNet. + +using Xunit; + +namespace UnitsNet.GlobalSetup.DefaultFirst.Tests; + +/// +/// Tests configuring the process-wide default setup after first use. +/// +/// +/// This assembly intentionally contains a single test because cannot be reset. +/// +public class UnitsNetSetupDefaultFirstTests +{ + [Fact] + public void ConfigureDefaults_AfterDefaultIsCreated_ThrowsInvalidOperationException() + { + UnitsNetSetup originalDefault = UnitsNetSetup.Default; + bool configurationInvoked = false; + + Assert.Throws(() => UnitsNetSetup.ConfigureDefaults(_ => configurationInvoked = true)); + + Assert.False(configurationInvoked); + Assert.Same(originalDefault, UnitsNetSetup.Default); + } +} diff --git a/UnitsNet.GlobalSetup.Tests/UnitsNet.GlobalSetup.Tests.csproj b/UnitsNet.GlobalSetup.Tests/UnitsNet.GlobalSetup.Tests.csproj new file mode 100644 index 0000000000..d4b2b5d35d --- /dev/null +++ b/UnitsNet.GlobalSetup.Tests/UnitsNet.GlobalSetup.Tests.csproj @@ -0,0 +1,32 @@ + + + + net10.0 + $(TargetFrameworks);net48 + latest + enable + true + enable + xunit + + + + ../UnitsNet.snk + false + true + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/UnitsNet.GlobalSetup.Tests/UnitsNetSetupGlobalConfigurationTests.cs b/UnitsNet.GlobalSetup.Tests/UnitsNetSetupGlobalConfigurationTests.cs new file mode 100644 index 0000000000..961acb2bdf --- /dev/null +++ b/UnitsNet.GlobalSetup.Tests/UnitsNetSetupGlobalConfigurationTests.cs @@ -0,0 +1,33 @@ +// Licensed under MIT No Attribution, see LICENSE file at the root. +// Copyright 2013 Andreas Gullberg Larsen (andreas.larsen84@gmail.com). Maintained at https://github.com/angularsen/UnitsNet. + +using UnitsNet.Units; +using Xunit; + +namespace UnitsNet.GlobalSetup.Tests; + +/// +/// Tests configuring the process-wide default setup before first use. +/// +/// +/// This assembly intentionally contains a single test because cannot be reset. +/// Keep additional global-configuration scenarios isolated in separate test processes. +/// +public class UnitsNetSetupGlobalConfigurationTests +{ + [Fact] + public void ConfigureDefaults_BeforeFirstUse_ConfiguresAndFreezesDefaultSetup() + { + Assert.Equal("configuration", + Assert.Throws(() => UnitsNetSetup.ConfigureDefaults(null!)).ParamName); + + UnitsNetSetup configured = UnitsNetSetup.ConfigureDefaults(builder => builder.WithQuantities([Mass.Info])); + + Assert.Same(configured, UnitsNetSetup.Default); + Assert.Same(configured.UnitAbbreviations, UnitAbbreviationsCache.Default); + Assert.Same(configured.UnitParser, UnitParser.Default); + Assert.Equal([Mass.Info], configured.Quantities.Infos); + Assert.Throws(() => configured.UnitParser.Parse("m")); + Assert.Throws(() => UnitsNetSetup.ConfigureDefaults(_ => { })); + } +} diff --git a/UnitsNet.slnx b/UnitsNet.slnx index 7c14e04985..a55c5f6990 100644 --- a/UnitsNet.slnx +++ b/UnitsNet.slnx @@ -43,6 +43,8 @@ + + diff --git a/UnitsNet/CustomCode/UnitsNetSetup.cs b/UnitsNet/CustomCode/UnitsNetSetup.cs index 4e20412cef..9bcb79b7ac 100644 --- a/UnitsNet/CustomCode/UnitsNetSetup.cs +++ b/UnitsNet/CustomCode/UnitsNetSetup.cs @@ -14,6 +14,13 @@ namespace UnitsNet; /// public sealed class UnitsNetSetup { + /// + /// Synchronizes the default builder swap and creation checks with the value creation already synchronized by . + /// + private static readonly object DefaultConfigurationLock = new(); + private static DefaultConfigurationBuilder _defaultConfigurationBuilder = new(); + private static readonly Lazy DefaultConfiguration = new(BuildDefault); + /// /// Builds a UnitsNet setup by selecting built-in or external quantity definitions. /// @@ -92,14 +99,12 @@ internal UnitsNetSetup Build() } } - static UnitsNetSetup() + private static UnitsNetSetup BuildDefault() { - IReadOnlyCollection quantityInfos = Quantity.DefaultProvider.Quantities; - - // note: in order to support the ConvertByAbbreviation, the unit converter should require a UnitParser in the constructor - var unitConverter = UnitConverter.CreateDefault(); - - Default = new UnitsNetSetup(quantityInfos, unitConverter); + lock (DefaultConfigurationLock) + { + return _defaultConfigurationBuilder.Build(); + } } /// @@ -116,6 +121,37 @@ public static UnitsNetSetup Create(Action configura return builder.Build(); } + /// + /// Configures and creates the global default setup before its first use. + /// + /// Configures the quantities included in the default setup. + /// The configured global default setup. + /// The default setup has already been created. + /// + public static UnitsNetSetup ConfigureDefaults(Action configuration) + { + if (configuration is null) throw new ArgumentNullException(nameof(configuration)); + + lock (DefaultConfigurationLock) + { + if (DefaultConfiguration.IsValueCreated) + { + throw new InvalidOperationException("The default configuration cannot be changed after it has been created."); + } + + var builder = new DefaultConfigurationBuilder(); + configuration(builder); + + if (DefaultConfiguration.IsValueCreated) + { + throw new InvalidOperationException("The default configuration was created while it was being configured."); + } + + _defaultConfigurationBuilder = builder; + return DefaultConfiguration.Value; + } + } + /// /// Create a new UnitsNet setup with the given quantities, their units and unit conversion functions between units. /// @@ -140,11 +176,14 @@ public UnitsNetSetup(IEnumerable quantityInfos, UnitConverter unit /// provided. /// /// + /// Call before first accessing this property to select a different quantity catalog.
+ ///
/// Manipulating this instance, such as adding new units or changing default unit abbreviations, will affect most /// usages of UnitsNet in the /// current AppDomain since the typical use is via static members and not providing a setup instance. ///
- public static UnitsNetSetup Default { get; } + /// + public static UnitsNetSetup Default => DefaultConfiguration.Value; /// /// Converts between units of a quantity, such as from meters to centimeters of a given length.