Skip to content

Conversation

Notallthatevil
Copy link

@Notallthatevil Notallthatevil commented Aug 15, 2025

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

Which contains and deprecated method as of .NET8. Microsoft has a recommended fix for that here.

This resolves the issue in #766

…time 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
@304NotModified
Copy link
Contributor

Thanks for the pull request!

Could you please add some unit tests?

- 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.
@Notallthatevil
Copy link
Author

I added a simple test that does fail on the main branch. Not sure there is a whole lot to test here. I did ensure that all tests pass, especially ones that take in nullable primitive types like int? as they need special handling with the new DefaultInstanceOfValueType functionality. If you have any specific scenarios you would like to see let me know.

@Notallthatevil
Copy link
Author

Is there anything still blocking this from being merged?

@Notallthatevil
Copy link
Author

I fixed the format issue.

@304NotModified
Copy link
Contributor

304NotModified commented Sep 4, 2025

Is there anything still blocking this from being merged?

Only a review I guess 😅

@@ -46,6 +46,15 @@ private object DefaultInstanceOfValueType(Type returnType)
return BoxedDouble;
}

return Activator.CreateInstance(returnType)!;
Copy link
Contributor

@304NotModified 304NotModified Sep 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm doubting if we should use Activator.CreateInstance(returnType)!; for non-stucts, to keep things 100% the same for the previous cases.

Although all tests works, so not sure.

@nsubstitute/core-team any opinion on this?

Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes a runtime error that occurred when using NSubstitute's Arg.Any<T>() matcher with structs that have non-default constructors. The issue arose because Activator.CreateInstance would invoke the constructor, causing the matcher to fail when comparing against the modified struct values.

  • Replaces Activator.CreateInstance with GetUninitializedObject to create zero-initialized structs
  • Adds special handling for Nullable<T> types to return null
  • Implements conditional compilation for .NET 5+ vs older frameworks

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/NSubstitute/Core/DefaultForType.cs Updates struct instantiation logic to use GetUninitializedObject instead of Activator.CreateInstance
tests/NSubstitute.Acceptance.Specs/ArgumentMatching.cs Adds regression test for struct with default constructor scenario

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants