In C, why can't a Liststring object be stored in a Listobject variable
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
In C#, every string is an object, but List<string> is not a List<object>. That is not a strange compiler limitation. It is a deliberate type-safety rule. If the assignment were allowed, you could insert non-string objects into a list that was supposed to contain only strings.
Why the Assignment Fails
This does not compile:
At first glance it looks reasonable. After all, string derives from object. The problem appears when mutation enters the picture.
If the assignment were legal, then this would also become legal:
But objects and names would refer to the same underlying list. That would mean an integer had been added to a List<string>, which breaks the type guarantee of the original collection.
List<T> Is Invariant
List<T> is invariant. That means List<Derived> is not convertible to List<Base>, and List<Base> is not convertible to List<Derived>, even when Derived inherits from Base.
This is necessary because List<T> is both readable and writable:
- you can read items out as
T - you can add items in as
T
Once a generic container allows writes, the type system has to prevent unsafe substitutions.
Covariance Works Only for Safe Read-Only Shapes
C# does support covariance for certain interfaces, such as IEnumerable<out T>, because they only expose values for reading.
This is safe because IEnumerable<T> does not let you insert arbitrary object values back into the sequence.
That is the key distinction: covariance is allowed when the API shape makes it safe.
If You Really Need List<object>, Copy the Items
If the goal is to create a separate list typed as object, project into a new list:
Now the two collections are different objects, so adding an integer to objects does not corrupt names.
Design APIs Around the Real Requirement
A lot of confusion comes from method signatures that ask for List<object> even though they only need to read values. In those cases, a read-only abstraction is usually better.
This design works because the method consumes a covariant interface rather than a mutable list.
Arrays Are a Historical Exception
Developers sometimes point out that string[] can be assigned to object[]. That is true in .NET, but arrays are covariant for historical reasons and can fail at runtime in exactly the kind of unsafe situation that generics try to prevent.
So this is legal but dangerous:
Generics were designed more strictly so the compiler can reject unsafe cases earlier.
Common Pitfalls
The biggest mistake is assuming subtype relationships automatically lift through generic containers. They do not unless the generic type is explicitly variant and used in a safe way.
Another issue is requiring List<object> where an IEnumerable<object> or another read-only interface would be enough. That forces callers into unnecessary conversions.
People also sometimes treat array covariance as evidence that the list conversion should work too. In reality, arrays are the older, weaker design and one reason generics were made stricter.
Finally, do not confuse string is object with List<string> is List<object>. Those are completely different type relationships.
Summary
- '
List<string>cannot be assigned toList<object>becauseList<T>is invariant.' - Allowing the assignment would make it possible to insert non-string values into a string list.
- Covariance works for read-only interfaces such as
IEnumerable<T>. - If you need a
List<object>, create a new copied list. - Prefer read-only abstractions in APIs when mutation is not required.

