From 5edb54ac972dd746e9747befe2d63e98dc80a022 Mon Sep 17 00:00:00 2001 From: Nate Tripp Date: Fri, 15 Aug 2025 10:09:39 -0600 Subject: [PATCH 1/4] Resolved a bug that would cause a struct to not match and throw a runtime error. When a struct had an empty constructor that modified a field/property to a non-default value. The matcher would fail and throw a runtime error. This bug fix attempts to fix that issue by essentially zero initializing the struct the same way that default(T) does through the use of a runtime helper method. Simply, we replace Activator.CreateInstance with GetUninitializedObject. The idea came from this post https://stackoverflow.com/questions/1005336/c-sharp-using-reflection-to-create-a-struct Which contains and deprecated method as of .NET8. Microsoft has a recommended fix for that here. https://learn.microsoft.com/en-us/dotnet/fundamentals/syslib-diagnostics/syslib0050 --- src/NSubstitute/Core/DefaultForType.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/NSubstitute/Core/DefaultForType.cs b/src/NSubstitute/Core/DefaultForType.cs index f1fde93a..d20909fc 100644 --- a/src/NSubstitute/Core/DefaultForType.cs +++ b/src/NSubstitute/Core/DefaultForType.cs @@ -46,6 +46,15 @@ private object DefaultInstanceOfValueType(Type returnType) return BoxedDouble; } - return Activator.CreateInstance(returnType)!; + // Need to have a special case for Nullable to return null. + if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + return null!; + } +#if NET5_0_OR_GREATER + return System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject(returnType); +#else + return System.Runtime.Serialization.FormatterServices.GetUninitializedObject(returnType); +#endif } } \ No newline at end of file From 276df59c9546f6717594c6d35ad967a5f37e9492 Mon Sep 17 00:00:00 2001 From: Nate Tripp Date: Fri, 15 Aug 2025 11:09:10 -0600 Subject: [PATCH 2/4] Add tests for struct with default constructor - Added test method `Any_on_struct_with_default_constructor_should_work` to ensure method calls with struct arguments do not throw exceptions. - Introduced `StructWithDefaultConstructor` struct: - Contains a property `Value`. - Has a default constructor initializing `Value` to 42. - Created `IWithStructWithDefaultConstructor` interface: - Declares `MethodWithStruct` that accepts `StructWithDefaultConstructor` as an argument. --- .../ArgumentMatching.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/NSubstitute.Acceptance.Specs/ArgumentMatching.cs b/tests/NSubstitute.Acceptance.Specs/ArgumentMatching.cs index b43ef848..5db80971 100644 --- a/tests/NSubstitute.Acceptance.Specs/ArgumentMatching.cs +++ b/tests/NSubstitute.Acceptance.Specs/ArgumentMatching.cs @@ -866,6 +866,26 @@ public void Does_support_out_method_with_base_override() Assert.That(outArg, Is.EqualTo(4)); } + [Test] + public void Any_on_struct_with_default_constructor_should_work() + { + var something = Substitute.For(); + Assert.DoesNotThrow(() => something.MethodWithStruct(Arg.Any())); + } + + public struct StructWithDefaultConstructor + { + public int Value { get; set; } + public StructWithDefaultConstructor() { + Value = 42; + } + } + + public interface IWithStructWithDefaultConstructor + { + void MethodWithStruct(StructWithDefaultConstructor arg); + } + [SetUp] public void SetUp() { From 1c121537fa28dfd4574dc584f36313e7468340c1 Mon Sep 17 00:00:00 2001 From: Nate Tripp Date: Thu, 4 Sep 2025 09:29:28 -0600 Subject: [PATCH 3/4] Fixed class constructor curly brace format --- tests/NSubstitute.Acceptance.Specs/ArgumentMatching.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/NSubstitute.Acceptance.Specs/ArgumentMatching.cs b/tests/NSubstitute.Acceptance.Specs/ArgumentMatching.cs index 5db80971..b0b2a16b 100644 --- a/tests/NSubstitute.Acceptance.Specs/ArgumentMatching.cs +++ b/tests/NSubstitute.Acceptance.Specs/ArgumentMatching.cs @@ -876,7 +876,8 @@ public void Any_on_struct_with_default_constructor_should_work() public struct StructWithDefaultConstructor { public int Value { get; set; } - public StructWithDefaultConstructor() { + public StructWithDefaultConstructor() + { Value = 42; } } From 28858999a867a6b78b0d2c9788c852a8be5d3467 Mon Sep 17 00:00:00 2001 From: Nathan Tripp Date: Wed, 17 Sep 2025 09:11:26 -0600 Subject: [PATCH 4/4] Refactor default value handling logic Updated `GetDefault` methods in `AutoObservableProvider.cs` and `AutoTaskProvider.cs` to use the `DefaultForType` class for determining default values, improving code modularity and reusability. --- src/NSubstitute/Routing/AutoValues/AutoObservableProvider.cs | 3 ++- src/NSubstitute/Routing/AutoValues/AutoTaskProvider.cs | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/NSubstitute/Routing/AutoValues/AutoObservableProvider.cs b/src/NSubstitute/Routing/AutoValues/AutoObservableProvider.cs index 713a5bde..8d768534 100644 --- a/src/NSubstitute/Routing/AutoValues/AutoObservableProvider.cs +++ b/src/NSubstitute/Routing/AutoValues/AutoObservableProvider.cs @@ -21,6 +21,7 @@ public bool CanProvideValueFor(Type type) => private static object? GetDefault(Type type) { - return type.GetTypeInfo().IsValueType ? Activator.CreateInstance(type) : null; + var defaultForType = new DefaultForType(); + return defaultForType.GetDefaultFor(type); } } diff --git a/src/NSubstitute/Routing/AutoValues/AutoTaskProvider.cs b/src/NSubstitute/Routing/AutoValues/AutoTaskProvider.cs index 13d365f3..a7be528b 100644 --- a/src/NSubstitute/Routing/AutoValues/AutoTaskProvider.cs +++ b/src/NSubstitute/Routing/AutoValues/AutoTaskProvider.cs @@ -1,3 +1,4 @@ +using NSubstitute.Core; using System.Reflection; namespace NSubstitute.Routing.AutoValues; @@ -32,6 +33,7 @@ public object GetValue(Type type) private static object? GetDefault(Type type) { - return type.GetTypeInfo().IsValueType ? Activator.CreateInstance(type) : null; + var defaultForType = new DefaultForType(); + return defaultForType.GetDefaultFor(type); } }