diff --git a/src/libraries/System.Linq/src/System/Linq/OfType.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/OfType.SpeedOpt.cs index f2649e706c8a0e..26d3a316a410db 100644 --- a/src/libraries/System.Linq/src/System/Linq/OfType.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/OfType.SpeedOpt.cs @@ -177,15 +177,12 @@ public override IEnumerable Select(Func s public override bool Contains(TResult value) { - // Avoid checking for IList when size-optimized because it keeps IList - // implementations which may otherwise be trimmed. Since List implements - // IList and List is popular, this could potentially be a lot of code. - if (!IsSizeOptimized && - !typeof(TResult).IsValueType && // don't box TResult - _source is IList list) - { - return list.Contains(value); - } + // It is tempting to delegate here to IList.Contains if _source is IList (especially + // if TResult is not a value type, as it would be boxed as an argument to Contains). + // And while that will be correct in most cases, if any of the items in the source + // compares equally with value but is not actually of type TResult, doing so would + // skip the type check implied by OfType(). Further, if IList is a multidim + // array, its IList.Contains will fail for non-1 ranks. We thus just iterate directly. foreach (object? item in _source) { diff --git a/src/libraries/System.Linq/tests/OfTypeTests.cs b/src/libraries/System.Linq/tests/OfTypeTests.cs index 9c4464f2bf6ce0..d059c67b40e64b 100644 --- a/src/libraries/System.Linq/tests/OfTypeTests.cs +++ b/src/libraries/System.Linq/tests/OfTypeTests.cs @@ -228,5 +228,55 @@ public void MultipleIterations() Assert.Equal(i + 1, count); } } + + [Fact] + public void MultiDimArray_OfType_Succeeds() + { + var array = new string[3, 4]; + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 4; j++) + { + array[i, j] = $"{i}{j}"; + } + } + + // ToArray + var result = array.OfType().ToArray(); + Assert.Equal(12, result.Length); + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 4; j++) + { + Assert.Equal($"{i}{j}", result[i * 4 + j]); + } + } + + // Contains + foreach (string s in array) + { + Assert.True(array.OfType().Contains(s)); + } + } + + [Fact] + public void OfType_Contains_FiltersByTypeEvenIfEqual() + { + List list = [new MySneakyObject1()]; + Assert.Empty(list.OfType()); + Assert.False(list.OfType().Contains(new MySneakyObject2())); + } + + private class MySneakyObject1 + { + public override bool Equals(object? obj) => obj is MySneakyObject1; + public override int GetHashCode() => 0; + } + + private class MySneakyObject2 : MySneakyObject1 + { + public override bool Equals(object? obj) => obj is MySneakyObject2; + public override int GetHashCode() => 0; + } } }