Why Can This C# Override Have a Different Return Type?

Oct 23, 2025  

I stumbled upon a piece of C# code the other day that made me do a double-take. It looked something like this.

At a glance, it seems to defy a fundamental rule of C# inheritance.

The “Problem” Code

Here’s a simplified version of what I saw:

 1using System;
 2using System.Linq;
 3using System.Collections.Generic;
 4
 5public class Program
 6{
 7    public static void Main()
 8    {
 9        var a = new Generic<FieldValue> ();
10        var b = new Generic<TextFieldValue> ();
11        
12        var fa = a.GetFieldValue ();
13        var fb = b.GetFieldValue ();
14        
15        Console.WriteLine ($"{fa} of type {fa.GetType ().Name}");
16        Console.WriteLine ($"{fb} of type {fb.GetType ().Name}");
17    }
18}
19
20/*****************************************************************************/
21
22public interface IStaticCreate
23{
24    static abstract FieldValue Create();
25}
26
27/*****************************************************************************/
28
29public record FieldValue(int Id)
30    : IStaticCreate
31{
32    static FieldValue IStaticCreate.Create() => new FieldValue (1);
33}
34
35public record TextFieldValue(int Id, string Text)
36    : FieldValue(Id), IStaticCreate
37{
38    static FieldValue IStaticCreate.Create() => new TextFieldValue (2, "bla");
39}
40
41/*****************************************************************************/
42
43public abstract class AbstractBase
44{
45    public abstract FieldValue GetFieldValue();
46}
47
48public class Generic<T> : AbstractBase
49    where T : FieldValue, IStaticCreate
50{
51    public override T GetFieldValue() // <-- This is the weird part
52    {
53        var result = T.Create();
54        return (T)result;
55    }
56}

Open in .NET Fiddle

When you run this, the output is:

1FieldValue { Id = 1 } of type FieldValue
2TextFieldValue { Id = 2, Text = bla } of type TextFieldValue

So, What’s Going On?

Look at the Generic<T> class. It inherits from AbstractBase, which has an abstract method:

1public abstract FieldValue GetFieldValue();

But the override in Generic<T> is:

1public override T GetFieldValue()

How is this allowed? I always thought an overriding method had to have the exact same signature as the method it’s overriding, including the return type.

It turns out this is a feature called Covariant Return Types.

The Answer: C# 9 Covariant Return Types

This feature was introduced in C# 9.0 (which shipped with .NET 5 back in November 2020).

Covariant return types allow an overriding method to return a type that is more derived than the return type of the base class method.

The magic that makes it all work is the generic constraint on Generic<T>:

1where T : FieldValue, IStaticCreate

This constraint guarantees to the compiler that whatever T is, it will always be a FieldValue or a class that inherits from FieldValue (like TextFieldValue).

Because the compiler has this guarantee, it knows that any T returned by the override is assignable to the FieldValue required by the base method. The rule is satisfied.

Why Is This Useful?

This isn’t just a neat trick; it’s incredibly useful for type safety.

Look at the Main method again:

1var b = new Generic<TextFieldValue> ();
2var fb = b.GetFieldValue ();

The compiler knows that b.GetFieldValue() returns a TextFieldValue, not just a FieldValue. The variable fb is correctly inferred as TextFieldValue. This means we can immediately access its Text property (fb.Text) without having to do an ugly and potentially unsafe cast.

Before C# 9.0, this code wouldn’t have compiled. You would have been forced to write the override like this:

1// The "old" way
2public override FieldValue GetFieldValue()
3{
4    var result = T.Create();
5    return result;
6}

…and then the caller would have to cast the result:

1// The "old" way at the call site
2var b = new Generic<TextFieldValue> ();
3var fb_base = b.GetFieldValue (); // fb_base is FieldValue
4var fb = (TextFieldValue)fb_base; // Nasty cast needed

So, if you see an override returning a more specific type than its base, don’t panic. It’s not a bug, it’s C# 9.0 making our lives just a little bit better.

Reference