diff --git a/src/BenchmarkDotNet/Parameters/ParameterComparer.cs b/src/BenchmarkDotNet/Parameters/ParameterComparer.cs index a1363de073..6cadf6a946 100644 --- a/src/BenchmarkDotNet/Parameters/ParameterComparer.cs +++ b/src/BenchmarkDotNet/Parameters/ParameterComparer.cs @@ -28,7 +28,16 @@ private int CompareValues(object x, object y) if (x != null && y != null && x.GetType() == y.GetType() && x is IComparable xComparable) { - return xComparable.CompareTo(y); + try + { + return xComparable.CompareTo(y); + } + // Some types, such as Tuple and ValueTuple, have a fallible CompareTo implementation which can throw if the inner items don't implement IComparable. + // See: https://github.com/dotnet/BenchmarkDotNet/issues/2346 + // For now, catch and ignore the exception, and fallback to string comparison below. + catch (ArgumentException ex) when (ex.Message.Contains("At least one object must implement IComparable.")) + { + } } // Anything else. diff --git a/tests/BenchmarkDotNet.Tests/ParameterComparerTests.cs b/tests/BenchmarkDotNet.Tests/ParameterComparerTests.cs index 0f01045b5c..2ef1bc8891 100644 --- a/tests/BenchmarkDotNet.Tests/ParameterComparerTests.cs +++ b/tests/BenchmarkDotNet.Tests/ParameterComparerTests.cs @@ -157,6 +157,70 @@ public void IComparableComparisionTest() Assert.Equal(4, ((ComplexParameter)sortedData[3].Items[0].Value).Value); } + [Fact] + public void ValueTupleWithNonIComparableInnerTypesComparisionTest() + { + var comparer = ParameterComparer.Instance; + var originalData = new[] + { + new ParameterInstances(new[] + { + new ParameterInstance(sharedDefinition, (new ComplexNonIComparableParameter(), 1), null) + }), + new ParameterInstances(new[] + { + new ParameterInstance(sharedDefinition, (new ComplexNonIComparableParameter(), 3), null) + }), + new ParameterInstances(new[] + { + new ParameterInstance(sharedDefinition, (new ComplexNonIComparableParameter(), 2), null) + }), + new ParameterInstances(new[] + { + new ParameterInstance(sharedDefinition, (new ComplexNonIComparableParameter(), 4), null) + }) + }; + + var sortedData = originalData.OrderBy(d => d, comparer).ToArray(); + + Assert.Equal(1, (((ComplexNonIComparableParameter, int))sortedData[0].Items[0].Value).Item2); + Assert.Equal(2, (((ComplexNonIComparableParameter, int))sortedData[1].Items[0].Value).Item2); + Assert.Equal(3, (((ComplexNonIComparableParameter, int))sortedData[2].Items[0].Value).Item2); + Assert.Equal(4, (((ComplexNonIComparableParameter, int))sortedData[3].Items[0].Value).Item2); + } + + [Fact] + public void TupleWithNonIComparableInnerTypesComparisionTest() + { + var comparer = ParameterComparer.Instance; + var originalData = new[] + { + new ParameterInstances(new[] + { + new ParameterInstance(sharedDefinition, Tuple.Create(new ComplexNonIComparableParameter(), 1), null) + }), + new ParameterInstances(new[] + { + new ParameterInstance(sharedDefinition, Tuple.Create(new ComplexNonIComparableParameter(), 3), null) + }), + new ParameterInstances(new[] + { + new ParameterInstance(sharedDefinition, Tuple.Create(new ComplexNonIComparableParameter(), 2), null) + }), + new ParameterInstances(new[] + { + new ParameterInstance(sharedDefinition, Tuple.Create(new ComplexNonIComparableParameter(), 4), null) + }) + }; + + var sortedData = originalData.OrderBy(d => d, comparer).ToArray(); + + Assert.Equal(1, ((Tuple)sortedData[0].Items[0].Value).Item2); + Assert.Equal(2, ((Tuple)sortedData[1].Items[0].Value).Item2); + Assert.Equal(3, ((Tuple)sortedData[2].Items[0].Value).Item2); + Assert.Equal(4, ((Tuple)sortedData[3].Items[0].Value).Item2); + } + private class ComplexParameter : IComparable, IComparable { public ComplexParameter(int value, string name) @@ -199,5 +263,9 @@ public int CompareTo(object obj) return CompareTo(other); } } + + private class ComplexNonIComparableParameter + { + } } } \ No newline at end of file