英文:
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<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>()'
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<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...
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论