泛型参数-使用具体类型进行编译,但是实现的接口不会编译

时间:2018-07-20 21:10:59

标签: c# generics interface

给出这些接口和类...

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符合该要求。

4 个答案:

答案 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>,这是不可能的。它们具有协变关系,但是彼此之间并不相同。