Skip to content

Commit 6f75ebc

Browse files
Fixed bug that would cause a struct to not match and throw a runtime error. (#895)
* 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 * 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. * Fixed class constructor curly brace format * 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.
1 parent c2381f4 commit 6f75ebc

File tree

4 files changed

+36
-3
lines changed

4 files changed

+36
-3
lines changed

src/NSubstitute/Core/DefaultForType.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,15 @@ private object DefaultInstanceOfValueType(Type returnType)
4646
return BoxedDouble;
4747
}
4848

49-
return Activator.CreateInstance(returnType)!;
49+
// Need to have a special case for Nullable<T> to return null.
50+
if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Nullable<>))
51+
{
52+
return null!;
53+
}
54+
#if NET5_0_OR_GREATER
55+
return System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject(returnType);
56+
#else
57+
return System.Runtime.Serialization.FormatterServices.GetUninitializedObject(returnType);
58+
#endif
5059
}
5160
}

src/NSubstitute/Routing/AutoValues/AutoObservableProvider.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public bool CanProvideValueFor(Type type) =>
2121

2222
private static object? GetDefault(Type type)
2323
{
24-
return type.GetTypeInfo().IsValueType ? Activator.CreateInstance(type) : null;
24+
var defaultForType = new DefaultForType();
25+
return defaultForType.GetDefaultFor(type);
2526
}
2627
}

src/NSubstitute/Routing/AutoValues/AutoTaskProvider.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using NSubstitute.Core;
12
using System.Reflection;
23

34
namespace NSubstitute.Routing.AutoValues;
@@ -32,6 +33,7 @@ public object GetValue(Type type)
3233

3334
private static object? GetDefault(Type type)
3435
{
35-
return type.GetTypeInfo().IsValueType ? Activator.CreateInstance(type) : null;
36+
var defaultForType = new DefaultForType();
37+
return defaultForType.GetDefaultFor(type);
3638
}
3739
}

tests/NSubstitute.Acceptance.Specs/ArgumentMatching.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -866,6 +866,27 @@ public void Does_support_out_method_with_base_override()
866866
Assert.That(outArg, Is.EqualTo(4));
867867
}
868868

869+
[Test]
870+
public void Any_on_struct_with_default_constructor_should_work()
871+
{
872+
var something = Substitute.For<IWithStructWithDefaultConstructor>();
873+
Assert.DoesNotThrow(() => something.MethodWithStruct(Arg.Any<StructWithDefaultConstructor>()));
874+
}
875+
876+
public struct StructWithDefaultConstructor
877+
{
878+
public int Value { get; set; }
879+
public StructWithDefaultConstructor()
880+
{
881+
Value = 42;
882+
}
883+
}
884+
885+
public interface IWithStructWithDefaultConstructor
886+
{
887+
void MethodWithStruct(StructWithDefaultConstructor arg);
888+
}
889+
869890
[SetUp]
870891
public void SetUp()
871892
{

0 commit comments

Comments
 (0)