Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Cannot override a method that returns a Task<T?>

Consider the following code:

public class A
{
    public virtual Task<T?> M<T>()
    {
        throw new NotImplementedException();
    }
}

public class B : A
{
    public override Task<T?> M<T>()
    {
        throw new NotImplementedException();
    }
}

This will not compile, with the following error:

CS0508: 'B.M<T>()': return type must be 'Task<T?>' to match overridden member 'A.M<T>()'

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

However, if A was an interface a similar code would compile with no error.
The issue does not seem to be specific to a particular version of C#.

What is the reason behind that?

>Solution :

TL;DR: Nullable reference types introduced some impedance mismatches into the language, particularly with respect to generics.

This is to do with nullable reference types. Before nullable reference types existed, this code wouldn’t compile at all, because T? wouldn’t be meaningful.

I don’t think you can define this in a way that makes sense for both T-as-a-reference-type and T-as-a-value-type (unless the notnull/default version at the bottom does that)… but if you choose which of those you want, you can constrain T appropriately:

Constraining T to be a non-nullable value type:

public abstract class A
{
    public abstract Task<T?> M<T>() where T : struct;
}

public class B : A
{
    public override Task<T?> M<T>() where T : struct
    {
        throw new NotImplementedException();
    }
}

Constraining T to be a reference type:

public abstract class A
{
    public abstract Task<T?> M<T>() where T : class;
}

public class B : A
{
    public override Task<T?> M<T>() where T : class
    {
        throw new NotImplementedException();
    }
}

I do agree it’s weird – and it gets even weirder with the notnull constraint, where you need to specify the default constraint on the override:

public abstract class A
{
    public abstract Task<T?> M<T>() where T : notnull;
}

public class B : A
{
    public override Task<T?> M<T>() where T : default
    {
        throw new NotImplementedException();
    }
}

I’ll readily admit I don’t fully understand what that means, unfortunately. The whole topic is really confusing…

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading