🔧 The Array That Let You Write the Wrong Type In, Just Not Keep It
C# arrays are covariant for reference types, which means a string array can be assigned to an object array variable without any cast, because every string genuinely is an object. The compiler allows this assignment and then has no further opinion about what you store through that object array reference – it will happily let you write code that attempts to put an int or a completely unrelated reference type into what is, underneath, still a string array. The check that actually enforces the real element type doesn’t happen at compile time at all; it happens on every single array store, at runtime, in the form of an ArrayTypeMismatchException.
🔎 The Problem
string[] names = { "Ada", "Grace" };
// Compiles fine: string[] is assignable to object[] (covariance)
object[] items = names;
items[0] = "Linus"; // fine - still a string
items[1] = 42; // compiles! object[] can legally hold an int...
// ...but blows up at runtime, because the ARRAY is still really a
// string[] underneath, and the CLR checks the real element type on
// every store through a covariant array reference:
// System.ArrayTypeMismatchException: Attempted to access an element
// as a type incompatible with the array.
void Fill(object[] target, object value)
{
for (int i = 0; i < target.Length; i++)
target[i] = value; // generic-looking code, same hidden risk
}
Fill(names, 42); // compiles cleanly, throws only when actually called
✅ Fix: Don’t Rely on Array Covariance When the Element Type Actually Matters
- Prefer a read-only generic sequence type (IReadOnlyList or IEnumerable) for parameters that only need to read elements – covariant interfaces give you the same flexibility without ever exposing a write path that can fail.
- If a method genuinely needs to write into a caller-supplied array, declare the parameter as the specific array type you need (string[], not object[]) so a type mismatch becomes a compile error instead of a runtime exception.
- When you must accept object[] for a generic-looking utility, document that it assumes the caller passed an actual object[] (or treat any write as something that can throw) rather than assuming covariance makes it safe.
⚠️ Why This Is Easy to Miss
- The code compiles without a single warning, because covariant array assignment is a deliberately legal C# feature – nothing in the type system flags the later write as suspicious.
- The failure only appears on the specific line that writes an incompatible element, which can be buried deep inside a generic-looking helper method that was written and tested only against its exact declared type.
Array covariance lets the compiler trust you with a promise the runtime was never willing to make – and the runtime always gets the last word.
