Skip to content

Commit b801537

Browse files
Remove IList.Contains use from OfType (#119608)
Co-authored-by: Stephen Toub <[email protected]>
1 parent 1c1e696 commit b801537

File tree

2 files changed

+56
-9
lines changed

2 files changed

+56
-9
lines changed

src/libraries/System.Linq/src/System/Linq/OfType.SpeedOpt.cs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -177,15 +177,12 @@ public override IEnumerable<TResult2> Select<TResult2>(Func<TResult, TResult2> s
177177

178178
public override bool Contains(TResult value)
179179
{
180-
// Avoid checking for IList when size-optimized because it keeps IList
181-
// implementations which may otherwise be trimmed. Since List<T> implements
182-
// IList and List<T> is popular, this could potentially be a lot of code.
183-
if (!IsSizeOptimized &&
184-
!typeof(TResult).IsValueType && // don't box TResult
185-
_source is IList list)
186-
{
187-
return list.Contains(value);
188-
}
180+
// It is tempting to delegate here to IList.Contains if _source is IList (especially
181+
// if TResult is not a value type, as it would be boxed as an argument to Contains).
182+
// And while that will be correct in most cases, if any of the items in the source
183+
// compares equally with value but is not actually of type TResult, doing so would
184+
// skip the type check implied by OfType<TResult>(). Further, if IList is a multidim
185+
// array, its IList.Contains will fail for non-1 ranks. We thus just iterate directly.
189186

190187
foreach (object? item in _source)
191188
{

src/libraries/System.Linq/tests/OfTypeTests.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,5 +228,55 @@ public void MultipleIterations()
228228
Assert.Equal(i + 1, count);
229229
}
230230
}
231+
232+
[Fact]
233+
public void MultiDimArray_OfType_Succeeds()
234+
{
235+
var array = new string[3, 4];
236+
for (int i = 0; i < 3; i++)
237+
{
238+
for (int j = 0; j < 4; j++)
239+
{
240+
array[i, j] = $"{i}{j}";
241+
}
242+
}
243+
244+
// ToArray
245+
var result = array.OfType<string>().ToArray();
246+
Assert.Equal(12, result.Length);
247+
for (int i = 0; i < 3; i++)
248+
{
249+
for (int j = 0; j < 4; j++)
250+
{
251+
Assert.Equal($"{i}{j}", result[i * 4 + j]);
252+
}
253+
}
254+
255+
// Contains
256+
foreach (string s in array)
257+
{
258+
Assert.True(array.OfType<string>().Contains(s));
259+
}
260+
}
261+
262+
[Fact]
263+
public void OfType_Contains_FiltersByTypeEvenIfEqual()
264+
{
265+
List<MySneakyObject1> list = [new MySneakyObject1()];
266+
Assert.Empty(list.OfType<MySneakyObject2>());
267+
Assert.False(list.OfType<MySneakyObject2>().Contains(new MySneakyObject2()));
268+
}
269+
270+
private class MySneakyObject1
271+
{
272+
public override bool Equals(object? obj) => obj is MySneakyObject1;
273+
public override int GetHashCode() => 0;
274+
}
275+
276+
private class MySneakyObject2 : MySneakyObject1
277+
{
278+
public override bool Equals(object? obj) => obj is MySneakyObject2;
279+
public override int GetHashCode() => 0;
280+
}
231281
}
232282
}

0 commit comments

Comments
 (0)