diff --git a/src/BenchmarkDotNet/Parameters/SmartParamBuilder.cs b/src/BenchmarkDotNet/Parameters/SmartParamBuilder.cs index 49ea1b162f..adc9789ef8 100644 --- a/src/BenchmarkDotNet/Parameters/SmartParamBuilder.cs +++ b/src/BenchmarkDotNet/Parameters/SmartParamBuilder.cs @@ -8,6 +8,7 @@ using BenchmarkDotNet.Extensions; using BenchmarkDotNet.Helpers; using BenchmarkDotNet.Reports; +using BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation; namespace BenchmarkDotNet.Parameters { @@ -33,9 +34,10 @@ internal static ParameterInstances CreateForArguments(MethodInfo benchmark, Para if (unwrappedValue is object[] array) { + Type firstParamType = benchmark.GetParameters().FirstOrDefault()?.ParameterType; // the user provided object[] for a benchmark accepting a single argument if (parameterDefinitions.Length == 1 && array.Length == 1 - && array[0]?.GetType() == benchmark.GetParameters().FirstOrDefault()?.ParameterType) // the benchmark that accepts an object[] as argument + && (array[0]?.GetType() == firstParamType || (firstParamType != null && firstParamType.IsStackOnlyWithImplicitCast(array[0])))) // the benchmark that accepts an object[] as argument { return new ParameterInstances( new[] { Create(parameterDefinitions, array[0], valuesInfo.source, sourceIndex, argumentIndex: 0, summaryStyle) }); @@ -91,16 +93,31 @@ public SmartArgument(ParameterDefinition[] parameterDefinitions, object value, M public string ToSourceCode() { - string cast = $"({parameterDefinitions[argumentIndex].ParameterType.GetCorrectCSharpTypeName()})"; // it's an object so we need to cast it to the right type + Type paramType = parameterDefinitions[argumentIndex].ParameterType; + bool isParamRefLike = RunnableReflectionHelpers.IsRefLikeType(paramType); + + string cast = isParamRefLike ? $"({Value.GetType().GetCorrectCSharpTypeName()})" + : $"({paramType.GetCorrectCSharpTypeName()})"; // it's an object so we need to cast it to the right type string callPostfix = source is PropertyInfo ? string.Empty : "()"; - string indexPostfix = parameterDefinitions.Length > 1 - ? $"[{argumentIndex}]" // IEnumerable - : string.Empty; // IEnumerable + MethodInfo sourceAsMethodInfo = source as MethodInfo; + PropertyInfo sourceAsPropertyInfo = source as PropertyInfo; + + Type indexableType = typeof(IEnumerable); + + string indexPostfix; + if (sourceAsMethodInfo?.ReturnType == indexableType || + sourceAsPropertyInfo?.GetMethod.ReturnType == indexableType) { + indexPostfix = $"[{argumentIndex}]"; + } + else + { + indexPostfix = string.Empty; // IEnumerable + } string methodCall; - if ((source as MethodInfo)?.IsStatic ?? (source as PropertyInfo)?.GetMethod.IsStatic ?? throw new Exception($"{nameof(source)} was not {nameof(MethodInfo)} nor {nameof(PropertyInfo)}")) + if (sourceAsMethodInfo?.IsStatic ?? sourceAsPropertyInfo?.GetMethod.IsStatic ?? throw new Exception($"{nameof(source)} was not {nameof(MethodInfo)} nor {nameof(PropertyInfo)}")) { // If the source member is static, we need to place the fully qualified type name before it, in case the source member is from another type that this generated type does not inherit from. methodCall = $"{source.DeclaringType.GetCorrectCSharpTypeName()}.{source.Name}"; diff --git a/tests/BenchmarkDotNet.IntegrationTests/ArgumentsTests.cs b/tests/BenchmarkDotNet.IntegrationTests/ArgumentsTests.cs index 021ff5d96d..c168071169 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/ArgumentsTests.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/ArgumentsTests.cs @@ -405,6 +405,29 @@ public void AcceptsSpan(Span span) } } + [Theory, MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)] + public void AnArrayFromArgumentsSourceCanBePassedToBenchmarkAsSpan(IToolchain toolchain) => CanExecute(toolchain); + + public class WithArrayFromArgumentsSourceToSpan + { + public IEnumerable GetArray() + { + yield return new object[] { new[] { 0, 1, 2 } }; + } + + [Benchmark] + [ArgumentsSource(nameof(GetArray))] + public void AcceptsSpanFromArgumentsSource(Span span) + { + if (span.Length != 3) + throw new ArgumentException("Invalid length"); + + for (int i = 0; i < 3; i++) + if (span[i] != i) + throw new ArgumentException("Invalid value"); + } + } + [TheoryEnvSpecific("The implicit cast operator is available only in .NET Core 2.1+ (See https://github.com/dotnet/corefx/issues/30121 for more)", EnvRequirement.DotNetCoreOnly)] [MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)] @@ -425,6 +448,31 @@ public void AcceptsReadOnlySpan(ReadOnlySpan notString) } } + [TheoryEnvSpecific("The implicit cast operator is available only in .NET Core 2.1+ (See https://github.com/dotnet/corefx/issues/30121 for more)", + EnvRequirement.DotNetCoreOnly)] + [MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)] + public void StringFromArgumentsSourceCanBePassedToBenchmarkAsReadOnlySpan(IToolchain toolchain) => CanExecute(toolchain); + + public class WithStringFromArgumentsSourceToReadOnlySpan + { + private const string expectedString = "very nice string"; + + public IEnumerable GetString() + { + yield return new object[] { expectedString }; + } + + [Benchmark] + [ArgumentsSource(nameof(GetString))] + public void AcceptsReadOnlySpanFromArgumentsSource(ReadOnlySpan notString) + { + string aString = notString.ToString(); + + if (aString != expectedString) + throw new ArgumentException("Invalid value"); + } + } + [Theory, MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)] public void AnArrayOfStringsCanBeUsedAsArgument(IToolchain toolchain) => CanExecute(toolchain);