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.