Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/net48-compatibility.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
2 changes: 2 additions & 0 deletions Build/build-functions.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net10.0</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('Windows'))">$(TargetFrameworks);net48</TargetFrameworks>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<IsTestProject>true</IsTestProject>
<Nullable>enable</Nullable>
<TestFramework>xunit</TestFramework>
</PropertyGroup>

<PropertyGroup>
<AssemblyOriginatorKeyFile>../UnitsNet.snk</AssemblyOriginatorKeyFile>
<DelaySign>false</DelaySign>
<SignAssembly>true</SignAssembly>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="../UnitsNet/UnitsNet.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Tests configuring the process-wide default setup after first use.
/// </summary>
/// <remarks>
/// This assembly intentionally contains a single test because <see cref="UnitsNetSetup.Default" /> cannot be reset.
/// </remarks>
public class UnitsNetSetupDefaultFirstTests
{
[Fact]
public void ConfigureDefaults_AfterDefaultIsCreated_ThrowsInvalidOperationException()
{
UnitsNetSetup originalDefault = UnitsNetSetup.Default;
bool configurationInvoked = false;

Assert.Throws<InvalidOperationException>(() => UnitsNetSetup.ConfigureDefaults(_ => configurationInvoked = true));

Assert.False(configurationInvoked);
Assert.Same(originalDefault, UnitsNetSetup.Default);
}
}
32 changes: 32 additions & 0 deletions UnitsNet.GlobalSetup.Tests/UnitsNet.GlobalSetup.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net10.0</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('Windows'))">$(TargetFrameworks);net48</TargetFrameworks>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<IsTestProject>true</IsTestProject>
<Nullable>enable</Nullable>
<TestFramework>xunit</TestFramework>
</PropertyGroup>

<PropertyGroup>
<AssemblyOriginatorKeyFile>../UnitsNet.snk</AssemblyOriginatorKeyFile>
<DelaySign>false</DelaySign>
<SignAssembly>true</SignAssembly>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="../UnitsNet/UnitsNet.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Tests configuring the process-wide default setup before first use.
/// </summary>
/// <remarks>
/// This assembly intentionally contains a single test because <see cref="UnitsNetSetup.Default" /> cannot be reset.
/// Keep additional global-configuration scenarios isolated in separate test processes.
/// </remarks>
public class UnitsNetSetupGlobalConfigurationTests
{
[Fact]
public void ConfigureDefaults_BeforeFirstUse_ConfiguresAndFreezesDefaultSetup()
{
Assert.Equal("configuration",
Assert.Throws<ArgumentNullException>(() => 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<UnitNotFoundException>(() => configured.UnitParser.Parse<LengthUnit>("m"));
Assert.Throws<InvalidOperationException>(() => UnitsNetSetup.ConfigureDefaults(_ => { }));
}
}
2 changes: 2 additions & 0 deletions UnitsNet.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
</Folder>
<Project Path="CodeGen/CodeGen.csproj" />
<Project Path="UnitsNet.Benchmark/UnitsNet.Benchmark.csproj" />
<Project Path="UnitsNet.GlobalSetup.DefaultFirst.Tests/UnitsNet.GlobalSetup.DefaultFirst.Tests.csproj" />
<Project Path="UnitsNet.GlobalSetup.Tests/UnitsNet.GlobalSetup.Tests.csproj" />
<Project Path="UnitsNet.NumberExtensions.CS14.Tests\UnitsNet.NumberExtensions.CS14.Tests.csproj" Type="Classic C#" />
<Project Path="UnitsNet.NumberExtensions.CS14\UnitsNet.NumberExtensions.CS14.csproj" Type="Classic C#" />
<Project Path="UnitsNet.NumberExtensions.Tests/UnitsNet.NumberExtensions.Tests.csproj" />
Expand Down
55 changes: 47 additions & 8 deletions UnitsNet/CustomCode/UnitsNetSetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ namespace UnitsNet;
/// </summary>
public sealed class UnitsNetSetup
{
/// <summary>
/// Synchronizes the default builder swap and creation checks with the value creation already synchronized by <see cref="Lazy{T}" />.
/// </summary>
private static readonly object DefaultConfigurationLock = new();
private static DefaultConfigurationBuilder _defaultConfigurationBuilder = new();
private static readonly Lazy<UnitsNetSetup> DefaultConfiguration = new(BuildDefault);

/// <summary>
/// Builds a UnitsNet setup by selecting built-in or external quantity definitions.
/// </summary>
Expand Down Expand Up @@ -92,14 +99,12 @@ internal UnitsNetSetup Build()
}
}

static UnitsNetSetup()
private static UnitsNetSetup BuildDefault()
{
IReadOnlyCollection<QuantityInfo> 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();
}
}

/// <summary>
Expand All @@ -116,6 +121,37 @@ public static UnitsNetSetup Create(Action<DefaultConfigurationBuilder> configura
return builder.Build();
}

/// <summary>
/// Configures and creates the global default setup before its first use.
/// </summary>
/// <param name="configuration">Configures the quantities included in the default setup.</param>
/// <returns>The configured global default setup.</returns>
/// <exception cref="InvalidOperationException">The default setup has already been created.</exception>
/// <seealso cref="Default" />
public static UnitsNetSetup ConfigureDefaults(Action<DefaultConfigurationBuilder> 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;
}
}

/// <summary>
/// Create a new UnitsNet setup with the given quantities, their units and unit conversion functions between units.
/// </summary>
Expand All @@ -140,11 +176,14 @@ public UnitsNetSetup(IEnumerable<QuantityInfo> quantityInfos, UnitConverter unit
/// provided.
/// </summary>
/// <remarks>
/// Call <see cref="ConfigureDefaults" /> before first accessing this property to select a different quantity catalog.<br />
/// <br />
/// 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.
/// </remarks>
public static UnitsNetSetup Default { get; }
/// <seealso cref="ConfigureDefaults" />
public static UnitsNetSetup Default => DefaultConfiguration.Value;

/// <summary>
/// Converts between units of a quantity, such as from meters to centimeters of a given length.
Expand Down
Loading