【问题标题】:Generic parameter - using a concrete type compiles, but an implemented interface does not通用参数 - 使用具体类型编译,但实现的接口不编译
【发布时间】:2018-07-20 21:10:59
【问题描述】:

鉴于这些接口和类...

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&lt;IPage&gt; 的返回类型与IEnumerable&lt;AboutPage&gt; 的返回类型不匹配。

我想创建一个将IPageAdminViewModel&lt;IPage&gt; 作为参数的方法。我想知道如何/是否可以使AboutPageAdminViewModel 符合该要求。

【问题讨论】:

  • 谢谢,我什至不知道 &lt;out T&gt; 是有效的语法!
  • 您是否有意在具体类中包含“基本接口”?它是通过扩展抽象类“隐式”实现的
  • 我试图通过明确显示未编译的位置/内容来使问题更容易理解。你认为删除它会更清楚吗?
  • 不一定,可以在问题本身添加此评论

标签: c# generics interface


【解决方案1】:

本质上,我不明白为什么IEnumerable&lt;IPage&gt; 的返回类型与IEnumerable&lt;AboutPage&gt; 的返回类型不匹配。

首先:您是正确的,该物业符合IEnumerable&lt;IPage&gt; 合同的所有要求。 C# 允许这样做是类型安全的。

你想要的特性叫做虚拟返回类型协方差,C#不支持。您可以通过比您的示例简单得多的示例来了解这一点:

class Animal {}
class Tiger : Animal {}
interface ICage { Animal GetAnimal(); }
class TigerCage : ICage { public Tiger GetAnimal() => new Tiger(); }

这是完全安全的。 ICage 要求实现者有一个返回动物的方法,而 TigerCage 有;它返回一个Tiger,它是一个Animal。这是安全的,但不合法。

C++ 有这个特性。 C# 没有。

人们要求这个功能已经 15 年多了;你可以在这个网站上搜索“C# return type covariance”,你会找到很多关于这个的答案(我和其他人)。

它从未实现过,因为 CLR 本身不支持它,有使用显式接口实现和阴影的简单解决方法,并且它在生态系统中引入了新类型的脆弱基类故障。由于这些和其他原因,与其他更有价值的功能相比,编译器团队的优先级一直较低。

如果需要,请在 GitHub 论坛上进行宣传;我相信你会有陪伴,但我也不会屏住呼吸等待编译器团队实现它;我们已经等了很久了!

顺便说一句,没有人要求虚拟参数类型逆变——也就是说,用一个采用动物的方法覆盖采用老虎的方法。但它同样安全。

【讨论】:

  • 谢谢你,埃里克,很好的解释。这是否意味着基本上不可能做我所追求的? (我想创建一个将IPageAdminViewModel&lt;IPage&gt; 作为参数的方法。我想知道如何/是否可以使AboutPageAdminViewModel 符合该要求)
  • @MartinHansenLennox:正如我在回答中所说,它没有在语言中实现的原因之一是因为有简单的解决方法。使用显式接口实现和阴影方法的组合来实现这一点。
  • 简单是相对的......但我正在努力解决。这意味着很多像你和乔恩这样的人愿意在这里闲逛并继续向像我这样的小矮人解释东西。
【解决方案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(); }

【讨论】:

  • 埃里克更详细地讨论了这个here
【解决方案3】:

问题不在于它是一个具体的类型。问题是它们必须匹配。

这两个都会编译:

public class AboutPageAdminViewModel
    : PageAdminViewModel<IPage>
    , IPageAdminViewModel<IPage> 
{
}


public class AboutPageAdminViewModel
    : PageAdminViewModel<AboutPage>
    , IPageAdminViewModel<AboutPage> 
{
}

如果它们不匹配,则该类必须找到一种方法来获得一个返回 IEnumerable&lt;IPage&gt;IEnumerable&lt;AboutPage&gt;Pages 属性,这是不可能的。它们具有协变关系,但它们彼此不同。

【讨论】:

  • 谢谢约翰。我想要一个服务,甚至可能是一个视图,它将IPageAdminViewModel&lt;IPage&gt; 作为参数。有没有办法让AboutPageAdminViewModel 满足这个要求?
【解决方案4】:

IPageAdminViewModel&lt;IPage&gt; 需要实现 IEnumerable&lt;IPage&gt; Pages { get; },而您的第一个示例“仅”实现 IEnumerable&lt;AboutPage&gt; Pages { get; }

虽然IEnumerable 是协变的,但这并不意味着它们是相同的并且只有一个不足以满足另一个的要求。

您的 &lt;out T&gt; 仅使 IPageAdminViewModel 协变,也就是说,您可以将该类型的实例分配给使用基本泛型类型定义的变量/属性。

【讨论】:

  • 谢谢阿米特。在你的答案,埃里克和约翰的答案之间,我终于明白了。看来我想做的事是不可能的。至少这样。 :(
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-05-16
  • 2018-10-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多