C# HashSet IntersectWith: In-Place Set Intersection
Learn how to use C# HashSet.IntersectWith to compute set intersection in place, with code examples, performance notes, and common pitfalls.
HashSet<T>.IntersectWith computes the intersection of the current set with another collection and updates the current set in place. Unlike LINQ's Intersect, which returns a new sequence, IntersectWith modifies the original set and returns void. This makes it useful when you want to filter an existing set without creating a new result set.
What IntersectWith Does
The method signature is public void IntersectWith(IEnumerable<T> other). It removes every element from the current set that is not also present in the other collection. After the call, the current set contains only the elements that existed in both the original set and other. If other is null, the method throws ArgumentNullException. The method does not return a value; it mutates the instance it is called on.
Basic Example
Consider two sets of integers. Calling IntersectWith on the first set with the second as the argument filters the first set down to the common elements.
var setA = new HashSet<int> { 1, 2, 3, 4, 5 }; var setB = new HashSet<int> { 4, 5, 6, 7 }; setA.IntersectWith(setB); Console.WriteLine(string.Join(", ", setA)); // Contains 4 and 5; HashSet order is not guaranteed
The original setA is modified. If you need to preserve the original set, create a copy before calling IntersectWith or use LINQ's Intersect to get a new sequence.
IntersectWith vs LINQ Intersect
LINQ's Enumerable.Intersect returns a new IEnumerable<T> and leaves the source collections untouched. The choice between the two depends on whether you want in-place mutation or a non-destructive query.
| Aspect | HashSet.IntersectWith | LINQ Intersect |
|---|---|---|
| Mutates source | Yes | No |
| Return value | void | IEnumerable<T> |
| Allocation | No new result set | New sequence and internal set |
| Best used when | You want to update an existing set | You need a separate result |
IntersectWith is often more efficient when you already have a HashSet and want to filter it in place, because it avoids allocating a new result set. LINQ Intersect is convenient when you want to keep the original data intact or you are working with any IEnumerable<T> and need a separate result.
Performance and Memory Behavior
The performance of IntersectWith depends on the type of other and on the .NET runtime version. In typical implementations, if other is a HashSet<T> with the same equality comparer, membership checks are O(1) on average, so the operation generally scales with the size of the current set. For other collection types, the method can still use the current set's hash table while examining other, giving behavior closer to O(n + m). Exact behavior varies by runtime version, so measure if this is a hot path.
Memory-wise, IntersectWith does not allocate a new result set. It removes elements from the existing set's internal storage. Some implementations may allocate temporary storage internally when other is not a HashSet<T>, but no new set is returned. This can reduce garbage collection pressure in hot paths where set intersection is performed frequently. However, if you need to retain the original set for later use, the copy required to preserve it may negate the memory benefit.
Common Pitfalls and Edge Cases
One common mistake is passing a null argument, which throws ArgumentNullException. Another is calling IntersectWith on an empty set; the result is an empty set, which is correct but may surprise developers expecting a different outcome.
If you are iterating over the set while calling IntersectWith, you will get an InvalidOperationException because the set is modified during enumeration. Always perform the intersection outside a foreach loop over the same set.
When using a custom IEqualityComparer<T> in the HashSet, the intersection is evaluated with the current set's comparer. If other is a collection that uses different equality semantics, only elements that match the current set's comparer are retained. For example, a case-sensitive set intersected with a case-insensitive collection will only match elements that are equal according to the set's comparer.
Choosing Between IntersectWith and Other Set Operations
HashSet<T> provides several in-place set operations: UnionWith, ExceptWith, SymmetricExceptWith, and IntersectWith. Each modifies the current set differently. UnionWith adds all elements from other, ExceptWith removes elements that are in other, and SymmetricExceptWith keeps only elements that appear in exactly one of the two sets. IntersectWith is the right choice when you need to keep only the common elements and you are willing to discard the rest.
Use IntersectWith when you have an existing HashSet that represents a working set and you want to filter it based on another collection without creating a new result object. If you need a non-destructive operation and want a separate sequence, prefer LINQ's Intersect. The in-place nature of IntersectWith can be a performance advantage in scenarios where allocations are costly, such as tight loops or high-throughput data processing.