无法重写返回 Task<T?> 的方法。

huangapple go评论69阅读模式
英文:

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

问题

这将无法编译,出现以下错误:

CS0508: 'B.M<T>()': 返回类型必须是 'Task<T?>' 以匹配重写的成员 'A.M<T>()'

然而,如果A是一个接口,类似的代码将会编译而不会出现错误。这个问题似乎与特定版本的C#无关。

这是因为C#中的接口允许在实现接口方法时使用协变(covariance)和逆变(contravariance),而类不允许这样做。在这种情况下,A是一个类,而B是它的子类,当B试图重写M<T>方法时,它的返回类型必须与基类A中的返回类型完全匹配。但是,如果A是一个接口,B可以使用协变来返回一个派生类型,从而不会产生编译错误。

英文:

Consider the following code:

public class A
{
    public virtual Task&lt;T?&gt; M&lt;T&gt;()
    {
        throw new NotImplementedException();
    }
}

public class B : A
{
    public override Task&lt;T?&gt; M&lt;T&gt;()
    {
        throw new NotImplementedException();
    }
}

This will not compile, with the following error:

CS0508: &#39;B.M&lt;T&gt;()&#39;: return type must be &#39;Task&lt;T?&gt;&#39; to match overridden member &#39;A.M&lt;T&gt;()&#39;

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?

答案1

得分: 3

这涉及可空引用类型。在可空引用类型出现之前,此代码根本无法编译,因为T?是没有意义的。

我认为你不能以一种方式定义它,使它对T既是引用类型又是值类型都有意义(除非底部的notnull/default版本可以实现这一点)。但是,如果你选择其中一个,可以适当地限定T

将T限定为非可空值类型:

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 a NotImplementedException();
    }
}

将T限定为引用类型:

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 a NotImplementedException();
    }
}

我确实认为这很奇怪,而且使用notnull约束时会变得更奇怪,其中你需要在覆盖时指定default约束:

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 a NotImplementedException();
    }
}

不幸的是,我承认我并不完全理解这意味着什么。整个主题真的令人困惑。

英文:

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&lt;T?&gt; M&lt;T&gt;() where T : struct;
}

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

Constraining T to be a reference type:

public abstract class A
{
    public abstract Task&lt;T?&gt; M&lt;T&gt;() where T : class;
}

public class B : A
{
    public override Task&lt;T?&gt; M&lt;T&gt;() 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&lt;T?&gt; M&lt;T&gt;() where T : notnull;
}

public class B : A
{
    public override Task&lt;T?&gt; M&lt;T&gt;() 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...

huangapple
  • 本文由 发表于 2023年3月10日 01:25:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/75688044.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定