【问题标题】:Shadowing Inherited Generic Interface Members in .NET: good, bad or ugly?在 .NET 中隐藏继承的通用接口成员:好、坏还是丑陋?
【发布时间】:2011-10-25 12:48:11
【问题描述】:

我知道,类实现中的隐藏成员可能会导致调用“错误”成员的情况,具体取决于我如何转换我的实例,但是对于接口,我认为这不是一个问题,我发现我自己经常写这样的接口:

public interface INode
{
    IEnumerable<INode> Children { get; }
}

public interface INode<N> : INode
    where N : INode<N>
{
    new IEnumerable<N> Children { get; }
}

public interface IAlpha : INode<IAlpha>
{ }

public interface IBeta : INode<IBeta>
{ }

我的代码中有一些地方只知道INode,所以孩子也应该是INode类型。

在其他地方我想了解具体类型 - 在我的示例 IAlphaIBeta 接口的实现中,我希望子项的类型与其父项相同。

所以我像这样实现NodeBase 类:

public abstract class NodeBase<N> : INode<N>
    where N : INode<N>
{
    protected readonly List<N> _children = new List<N>();

    public IEnumerable<N> Children
    {
        get { return _children.AsEnumerable(); }
    }

    IEnumerable<INode> INode.Children
    {
        get { return this.Children.Cast<INode>(); }
    }
}

在实际实现中没有阴影,仅在接口中。

IAlphaIBeta 的具体实例如下所示:

public class Alpha : NodeBase<Alpha>, IAlpha
{
    IEnumerable<IAlpha> INode<IAlpha>.Children
    {
        get { return this.Children.Cast<IAlpha>(); }
    }
}

public class Beta : NodeBase<Beta>, IBeta
{
    IEnumerable<IBeta> INode<IBeta>.Children
    {
        get { return this.Children.Cast<IBeta>(); }
    }
}

同样,在实现中没有阴影。

我现在可以像这样访问这些类型:

var alpha = new Alpha();
var beta = new Beta();

var alphaAsIAlpha = alpha as IAlpha;
var betaAsIBeta = beta as IBeta;

var alphaAsINode = alpha as INode;
var betaAsINode = beta as INode;

var alphaAsINodeAlpha = alpha as INode<Alpha>;
var betaAsINodeBeta = beta as INode<Beta>;

var alphaAsINodeIAlpha = alpha as INode<IAlpha>;
var betaAsINodeIBeta = beta as INode<IBeta>;

var alphaAsNodeBaseAlpha = alpha as NodeBase<Alpha>;
var betaAsNodeBaseBeta = beta as NodeBase<Beta>;

现在每个变量都有正确的强类型Children 集合。

所以,我的问题很简单。使用这种模式的界面成员的阴影是好是坏还是丑陋?为什么?

【问题讨论】:

  • 为什么不简单地使用类型参数来确定孩子的类型。然后 INode 仍然具有相同的语义,但您根本不需要阴影
  • @Rune - “类型参数”是什么意思?
  • @Enigmativity:我已经编辑了我的答案以简化 NodeBase 对 INode.Children 的实现——我已经弄清楚了我之前缺少的东西。
  • @Jon - 谢谢。非常感谢。

标签: c# .net oop generics shadowing


【解决方案1】:

我会说你有一个相当复杂的场景,我通常尝试让事情比这更简单 - 但如果它适合你,我认为添加更多信息是可以的像这样。 (在你到达IAlphaIBeta 位之前,这似乎是合理的;没有这些接口,AlphaBeta 根本不需要任何实现,调用者可以只使用INode&lt;IAlpha&gt;INode&lt;IBeta&gt;而是。

请特别注意IEnumerable&lt;T&gt; 有效地做同样的事情 - 诚然,不是将一个泛型与另一个隐藏起来,而是用泛型隐藏一个非泛型。

其他四点:

  • 您在NodeBase 中拨打AsEnumerable 毫无意义;来电者仍然可以转换为List&lt;T&gt;。如果您想防止这种情况发生,您可以执行Select(x =&gt; x) 之类的操作。 (理论上Skip(0) 可能可以工作,但可以被优化掉;LINQ to Objects 并没有很好地记录哪些运算符可以保证隐藏原始实现。Select 保证不会。实际上,Take(int.MaxValue) 也可以。)

  • 从 C# 4 开始,由于协方差,您的两个“叶子”类可以简化:

    public class Alpha : NodeBase<Alpha>, IAlpha
    {
        IEnumerable<IAlpha> INode<IAlpha>.Children { get { return Children; } }
    }
    
    public class Beta : NodeBase<Beta>, IBeta
    {
        IEnumerable<IBeta> INode<IBeta>.Children { get { return Children; } }
    }
    
  • 从 C# 4 开始,INode.ChildrenNodeBase 实现可以简化如果您愿意将 N 限制为引用类型:

    public abstract class NodeBase<N> : INode<N>
        where N : class, INode<N> // Note the class constraint
    {
        ...
    
        IEnumerable<INode> INode.Children
        {
            get { return this.Children; }
        }
    }
    
  • 从 C# 4 开始,您可以在 N 中声明 INode&lt;N&gt; 是协变的:

    public interface INode<out N> : INode
    

【讨论】:

  • 感谢另一个出色的答案。关于AsEnumerable 可转换回List&lt;T&gt; 的观点让我震惊。在 Rx 中,AsObservable 方法确实隐藏了源 observable。我只是假设AsEnumerable 也这样做了。也许 Rx 的人很聪明。
【解决方案2】:

为什么不简单地使用类型参数(即泛型类型的参数)来确定孩子的类型。然后 INode 仍然具有相同的语义,但您根本不需要阴影 而且您确实在实施转换为 INode 时有阴影,这将导致您在帖子中描述的相同问题

【讨论】:

    猜你喜欢
    • 2010-11-17
    • 1970-01-01
    • 2012-02-13
    • 2010-09-05
    • 2011-04-03
    • 2011-10-10
    • 1970-01-01
    • 2011-03-22
    • 1970-01-01
    相关资源
    最近更新 更多