Skip to content

Difference in Received() check between array params and new C# 13 params #905

@dusk0r

Description

@dusk0r

C# 13 allows using params with many collection types.
https://devblogs.microsoft.com/dotnet/csharp13-calling-methods-is-easier-and-faster/

Example:

void DoSomething2(params IEnumerable<object> values) {}

DoSomething2(new object(), new object());

The compiler will automatically create the required collection.
This causes an inconsistency between checking with classic array params and the new collections params:

public interface ITest
{
    void DoSomething(params object[] args);
    void DoSomething2(params IEnumerable<object> values); // valid since C# 13
}

public class Test
{
    public static void Run()
    {
        var obj = new Object();
        
        var substitute = Substitute.For<ITest>();
        substitute.DoSomething(obj);
        substitute.DoSomething2(obj);
 
        // Works
        substitute.Received().DoSomething(obj);
        // Fails 
        substitute.Received().DoSomething2(obj);
        // Works, but verbose
        substitute.Received()
            .DoSomething2(Arg.Is<IEnumerable<object>>(args => args.SequenceEqual(new List<object> { obj })));
        
        /*
         * NSubstitute.Exceptions.ReceivedCallsException: Expected to receive a call matching:
         *       DoSomething2(<>z__ReadOnlySingleElementList<Object>)
         * Actually received no matching calls.
         * Received 1 non-matching call (non-matching arguments indicated with '*' characters):
         *       DoSomething2(*<>z__ReadOnlySingleElementList<Object>*)
         */
    }
}

I wouldn't call it a bug but this is certainly unexpected.
Is there a simpler way to do the check?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions