No Explicit Conversion In Foreach
ID |
csharp.no_explicit_conversion_in_foreach |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
CSharp |
Tags |
reliability, suspicious-cast, type-conversion |
Description
Reports a foreach whose declared loop-variable type cannot be reached from the collection’s
element type by an implicit conversion. The language silently inserts a cast of every element, so
the loop throws InvalidCastException on the first element that does not fit.
Rationale
foreach is specified to convert each element to the declared loop-variable type. When that
conversion is implicit — iterating a List<Dog> as Animal or as an interface Dog implements —
nothing can fail. When it is not, the compiler still accepts the loop and emits a cast, and the
failure is deferred to run time.
Two things make that failure expensive. It is partial: elements before the offending one have
already been processed, so a loop that writes to a database or a file leaves the work half done and
the exception gives no hint about how far it got. And it is invisible at the call site: there is no
cast operator in the source, so a reader sees a plain loop, not a downcast. Compare with the
explicit alternative, foreach (var a in animals) { var d = (Dog) a; … }, where the cast is
visible and can be replaced with a pattern match.
A loop that intends to process only some elements should say so, with OfType<T>() or a type
pattern, rather than relying on every element happening to be of the derived type.
The rule reports only conversions it can decide from the file being analysed. The collection must be
a variable declared with an explicit array or single-type-argument collection type, and the element
type together with its whole supertype chain must be declared in the same compilation unit. When the
hierarchy leaves the file, an unseen base class or interface could make the conversion implicit, so
the rule stays silent rather than guessing. Loops over var collections, method results and LINQ
chains are likewise not analysed.
using System.Collections.Generic;
using System.Linq;
public class Animal { }
public class Dog : Animal { }
public class Cat : Animal { }
public class Kennel
{
public void Walk(List<Animal> animals)
{
foreach (Dog dog in animals) // FLAW — casts every element, throws on the first Cat
{
Feed(dog);
}
}
public void WalkSafely(List<Animal> animals)
{
foreach (Animal animal in animals) // OK — implicit upcast, cannot fail
{
Feed(animal);
}
foreach (Dog dog in animals.OfType<Dog>()) // OK — non-dogs are filtered out, not cast
{
Feed(dog);
}
}
private void Feed(Animal animal) { }
}
Remediation
Pick the option that matches the intent:
-
All elements really are of the derived type — fix the collection’s declared element type so the loop needs no conversion at all.
-
Only some elements are of interest — filter with
OfType<T>(), or iterate withvarand use a type pattern (if (animal is Dog dog)) so non-matching elements are skipped instead of throwing. -
A mismatch is a genuine error — iterate with
var, cast explicitly, and let the cast document the assumption at the point where it is made.