给出这些接口和类...
public interface IPage
{
string PageTitle { get; set; }
string PageContent { get; set; }
}
public abstract class Page
: IPage
{
public string PageTitle { get; set; }
public string PageContent { get; set; }
}
public class AboutPage
: Page
, IPage
{
}
public interface IPageAdminViewModel<out T>
where T : IPage
{
IEnumerable<T> Pages { get; }
}
public abstract class PageAdminViewModel<T>
: IPageAdminViewModel<T>
where T: IPage
{
public IEnumerable<T> Pages { get; set; }
}
为什么不使用IPage
作为接口类型参数进行编译。...
public class AboutPageAdminViewModel
: PageAdminViewModel<AboutPage>
, IPageAdminViewModel<IPage> // ERROR HERE - I realise this declaration is not required, just indicating where the error is (not)
{
}
“ AboutPageAdminViewModel”未实现接口成员“ IPageAdminViewModel
.Pages”。 'PageAdminViewModel .Pages'无法实现'IPageAdminViewModel .Pages',因为它没有匹配的返回类型'IEnumerable '。
....使用具体类AboutPage
时是什么?
public class AboutPageAdminViewModel
: PageAdminViewModel<AboutPage>
, IPageAdminViewModel<AboutPage> // NO ERROR - I realise this declaration is not required, just indicating where the error is (not)
{
}
从本质上讲,我不理解为什么IEnumerable<IPage>
的返回类型与IEnumerable<AboutPage>
的返回类型不匹配。
我想创建一个以IPageAdminViewModel<IPage>
作为参数的方法。我想知道如何/是否可以使AboutPageAdminViewModel
符合该要求。
答案 0 :(得分:8)
从本质上讲,我不理解为什么
IEnumerable<IPage>
的返回类型与IEnumerable<AboutPage>
的返回类型不匹配。
首先,您正确的是,该财产符合IEnumerable<IPage>
合同的所有要求。 C#允许这样做是类型安全的。
所需的功能称为虚拟返回类型协方差,而C#不支持。您可以通过比您的示例简单得多的示例来看到这一点:
class Animal {}
class Tiger : Animal {}
interface ICage { Animal GetAnimal(); }
class TigerCage : ICage { public Tiger GetAnimal() => new Tiger(); }
这是完全安全的。 ICage
要求实现者具有返回动物的方法,而TigerCage
则需要;它返回Tiger
的{{1}}。这是安全的,但不合法。
C ++具有此功能。 C#没有。
人们一直要求这个功能超过15年;您可以在此站点上搜索“ C#返回类型协方差”,并且您会发现许多有关此问题的答案(由我和他人)。
它从来没有实现过,因为CLR本身不支持它,使用显式接口实现和影子化有很容易的解决方法,并且将新的脆性基类故障引入了生态系统。由于这些原因和其他原因,与其他更有价值的功能相比,对于编译器团队而言,它始终是低优先级的。
如果需要,请在GitHub论坛上进行倡导;我确定您会陪伴,但是我也不会屏住呼吸等待编译器团队实施它。我们已经等了很久了!
顺便说一句,没人问过虚拟参数类型相反,也就是说,用带有Animal的方法覆盖采用Tiger的方法。但这同样安全。
答案 1 :(得分:2)
举起Eric的例子:
class Animal {}
class Tiger : Animal {}
interface ICage {
Animal GetAnimal(); }
class TigerCage : ICage {
//Won’t compile
public Tiger GetAnimal() =>
new Tiger(); }
您可以通过实现显式接口成员和强类型方法来解决此问题,其中一种将委托给另一种以避免代码重复:
class TigerCage: ICage {
Animal ICage.GetAnimal() => GetAnimal();
public Tiger GetAnimal() => new Tiger(); }
答案 2 :(得分:1)
IPageAdminViewModel<IPage>
需要实现IEnumerable<IPage> Pages { get; }
,而第一个示例“仅”实现IEnumerable<AboutPage> Pages { get; }
。
IEnumerable
是协变的,但这并不意味着它们是相同的,并且其中一个不足以满足另一个条件。
您的<out T>
仅使IPageAdminViewModel
协变,也就是说,您可以将类型的实例分配给使用基本泛型类型定义的变量/属性。
答案 3 :(得分:1)
问题不在于它是具体类型。问题在于它们必须匹配。
这两个都将编译:
public class AboutPageAdminViewModel
: PageAdminViewModel<IPage>
, IPageAdminViewModel<IPage>
{
}
public class AboutPageAdminViewModel
: PageAdminViewModel<AboutPage>
, IPageAdminViewModel<AboutPage>
{
}
如果它们不匹配,则该类将必须找到一种方法来拥有Pages
属性,该属性同时返回IEnumerable<IPage>
和IEnumerable<AboutPage>
,这是不可能的。它们具有协变关系,但是彼此之间并不相同。