C#

out T vs T in Generics

25 September 2026 · 5 min read

out T vs T in Generics

Navigating the world of generics in C and other languages can be tricky, especially when encountering concepts like covariance and contravariance, represented by <out T> and <in T>. Understanding these concepts is crucial for writing flexible and reusable code. This post delves into the differences between <out T> (covariance) and <T> (invariance), exploring their use cases and providing practical examples to solidify your understanding. Mastering these concepts will undoubtedly level up your programming skills and allow you to design more robust and adaptable software.

What is <out T> (Covariance)?

Covariance allows you to use a more derived type than originally specified. Imagine you have a generic interface IEnumerable<T>. Thanks to <out T>, an IEnumerable<string> can be treated as an IEnumerable<object>. This is because string is a more derived type than object. This flexibility simplifies working with collections and generic types.

A key benefit of covariance is enhanced code reusability. You can write methods that operate on generic interfaces without needing to know the exact type parameter. This promotes a more flexible and maintainable codebase.

For example:

IEnumerable<string> strings = new List<string> { "hello", "world" }; IEnumerable<object> objects = strings; // This is valid due to covariance 

What is <T> (Invariance)?

Invariance means the type parameter must match exactly. With an invariant generic type like List<T>, a List<string> cannot be treated as a List<object>, even though string derives from object. This restriction ensures type safety, preventing potential runtime errors caused by assigning incompatible types.

While invariance might seem restrictive, it’s essential for maintaining type integrity. It guarantees that operations performed on a generic type are valid for the specific type parameter, preventing unexpected behavior.

Consider this scenario:

List<string> strings = new List<string>(); // List<object> objects = strings; // This would be an error due to invariance 

When to Use Covariance and Invariance

Use <out T> when you only need to read data from the generic type. Common scenarios include returning data from a method or using generic interfaces like IEnumerable<T>. Invariance (<T>) is the default and necessary when you need both read and write access to the generic type, ensuring type safety.

Choosing the right variance type is essential for writing efficient and type-safe code. Understanding the underlying principles of covariance and invariance helps prevent unexpected behavior and ensures your code works as intended.

  • Covariance: Read-only operations
  • Invariance: Read and write operations

Practical Examples and Use Cases

Consider a scenario where you’re working with a repository pattern. Covariance allows you to define a generic interface for reading data, enabling flexibility in retrieving different types of objects. For instance, you could have a IReadOnlyRepository<out T> interface.

On the other hand, when implementing a repository for writing data, invariance ensures type safety. A generic interface like IRepository<T> would be invariant to prevent assigning incompatible types.

  1. Define IReadOnlyRepository<out T>
  2. Implement concrete repositories for specific types.
  3. Use covariance for read operations.

Here’s an example illustrating how covariance works with IEnumerable<T>:

// Covariance with IEnumerable<T> public void PrintItems(IEnumerable<object> items) { foreach (var item in items) { Console.WriteLine(item); } } IEnumerable<string> strings = new List<string>() { "one", "two" }; PrintItems(strings); // Valid due to covariance 

“Effective use of generics significantly enhances code reusability and type safety,” - Anders Hejlsberg, creator of C.

Placeholder for infographic illustrating covariance and invariance.

Learn more about generics.FAQ

Q: What are the key differences between <out T> and <T>?

A: <out T> (covariance) allows you to use more derived types, while <T> (invariance) requires an exact type match.

By understanding the nuances of <out T> and <T>, you can write more efficient, reusable, and type-safe code. This knowledge empowers you to leverage the full potential of generics in your projects, creating more adaptable and robust applications. Explore further resources on variance in C and other languages to deepen your understanding and refine your programming skills. Check out these helpful resources: Microsoft’s Generics Documentation, Oracle’s Java Generics Tutorial, and Kotlin’s Generics Overview.

Question & Answer :
What is the difference between <out T> and <T>? For example:

public interface IExample<out T> { ... } 

vs.

public interface IExample<T> { ... } 

The out keyword in generics is used to denote that the type T in the interface is covariant. See Covariance and contravariance for details.

The classic example is IEnumerable<out T>. Since IEnumerable<out T> is covariant, you’re allowed to do the following:

IEnumerable<string> strings = new List<string>(); IEnumerable<object> objects = strings; 

The second line above would fail if this wasn’t covariant, even though logically it should work, since string derives from object. Before variance in generic interfaces was added to C# and VB.NET (in .NET 4 with VS 2010), this was a compile time error.

After .NET 4, IEnumerable<T> was marked covariant, and became IEnumerable<out T>. Since IEnumerable<out T> only uses the elements within it, and never adds/changes them, it’s safe for it to treat an enumerable collection of strings as an enumerable collection of objects, which means it’s covariant.

This wouldn’t work with a type like IList<T>, since IList<T> has an Add method. Suppose this would be allowed:

IList<string> strings = new List<string>(); IList<object> objects = strings; // NOTE: Fails at compile time 

You could then call:

objects.Add(7); // This should work, since IList<object> should let us add **any** object 

This would, of course, fail - so IList<T> can’t be marked covariant.

There is also, btw, an option for in - which is used by things like comparison interfaces. IComparer<in T>, for example, works the opposite way. You can use a concrete IComparer<Foo> directly as an IComparer<Bar> if Bar is a subclass of Foo, because the IComparer<in T> interface is contravariant.