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

时间:2011-08-12 05:04:48

标签: c# .net 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>
{ }

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

在其他地方,我想了解具体的类型 - 在我的示例IAlpha&amp;的实现中。 IBeta接口我希望孩子的输入与父母一样。

所以我实现了一个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>(); }
    }
}

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

IAlpha&amp;的具体实例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>(); }
    }
}

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

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

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集合。

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

2 个答案:

答案 0 :(得分:8)

我会说你自己有一个相当复杂的场景,我通常尝试让事情变得简单 - 但如果它适合你,我认为可以添加更多信息像这样。 (在你到达IAlphaIBeta位之前似乎是合理的;没有这些接口,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; } }
    }
    
  • 从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; } } } 限制为参考类型:

    INode<N>
  • 从C#4开始,您可以在N中声明public interface INode<out N> : INode 是协变的:

    {{1}}

答案 1 :(得分:0)

为什么不简单地使用类型参数(这是泛型类型的参数)来确定子类型。然后INode仍然会有相同的sematics,但你根本不需要阴影 而且你确实在实现中投影阴影到INode将导致你在帖子中描述的相同问题