No Collection Passed To Own Method
ID |
csharp.no_collection_passed_to_own_method |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
CSharp |
Tags |
collection, reliability, suspicious-argument |
Description
Reports a collection or sequence operation that receives, as an argument, the collection it is called on. Set and sequence combinators degenerate when both operands are the same collection, so the result stops depending on the contents.
Rationale
items.Union(items) hands back items. items.Except(items) is always empty.
set.ExceptWith(set) always clears the set, set.UnionWith(set) always does nothing, and
items.SequenceEqual(items) is always true. None of these is a plausible intent, and none will
fail loudly: the code compiles, runs, and quietly produces a constant where a real comparison or
combination was expected. The usual cause is copy-paste or an autocompletion that picked the
nearest name — the second operand was meant to be the other collection.
AddRange and Concat are excluded on purpose. Passing a collection to either genuinely means
"append a second copy of myself", which is unusual but well defined and occasionally what the
author wants.
Receiver and argument must both be name paths: an identifier, this, or a chain of member accesses
over either, such as this._tags or _cache.Keys. A call or an indexer on either side is not
reported, because its two evaluations need not give back the same collection. Two plain identifiers
are compared by resolved symbol whenever the symbol table has both, so two same-named variables in
different scopes are not confused with each other; longer paths are compared by their parsed tokens.
public class Merger
{
private HashSet<string> _tags = new HashSet<string>();
private Dictionary<string, string> _cache = new Dictionary<string, string>();
public void Combine(List<string> left, List<string> right, HashSet<string> tags)
{
var all = left.Union(left); // FLAW — the union of a list with itself
var none = left.Except(left); // FLAW — always empty
tags.ExceptWith(tags); // FLAW — always clears the set
var same = this._tags.SequenceEqual(this._tags); // FLAW — always true
var empty = _cache.Keys.Except(_cache.Keys); // FLAW — always empty
var merged = left.Union(right); // OK — two different operands
left.AddRange(left); // OK — doubling a list is a real operation
var twice = left.Concat(left); // OK — repeating a sequence is a real operation
}
}
Remediation
Replace the argument with the collection that was meant to be the second operand. If the
degenerate result really is what the code wants, write it directly and say so: an empty sequence
instead of Except against itself, true instead of SequenceEqual against itself, Clear()
instead of ExceptWith against itself. The direct form states the intent and cannot be mistaken
for a bug.