在.NET中隐藏继承的通用接口成员:好,坏还是难看?

Eni*_*ity 9 .net c# oop generics shadowing

我知道类实现中的阴影成员会导致"错误"成员可以被调用的情况,这取决于我如何构建我的实例,但是对于接口我没有看到这可能是一个问题,我发现自己编写接口像这样经常这样:

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>
{ }
Run Code Online (Sandbox Code Playgroud)

我的代码中只有一些地方知道,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>(); }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

IAlpha&的具体实例IBeta如下:

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>(); }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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>;
Run Code Online (Sandbox Code Playgroud)

这些变量中的每一个现在都具有正确的强类型Children集合.

所以,我的问题很简单.界面成员的阴影是否使用这种模式好,坏或难看?为什么?

Jon*_*eet 8

我会说你在那里遇到了一个相当复杂的场景,我通常会尝试让事情变得简单 - 但如果它适合你,我认为可以添加更多这样的信息.(直到你到达这似乎是合理的IAlpha,并IBeta没有这些接口;位AlphaBeta不需要任何实现的一切,呼叫者可以只使用INode<IAlpha>INode<IBeta>替代.

特别要注意的是,IEnumerable<T>有效地做了同样的事情 - 不可否认,将一个泛型隐藏在另一个泛型中,但隐藏了非泛型的泛型.

其他四点:

  • 您对通话AsEnumerableNodeBase是没有意义的; 来电者仍然可以演员List<T>.如果你想阻止它,你可以做类似的事情Select(x => 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; } }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 从C#4开始,如果您愿意将其限制为引用类型NodeBase,INode.Children则可以简化您的实现: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; }
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 从C#4开始,您可以声明INode<N>在以下方面是协变的N:

    public interface INode<out N> : INode
    
    Run Code Online (Sandbox Code Playgroud)