9

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

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所以 children 也应该是 type 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>(); }
    }
}

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

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>(); }
    }
}

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

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

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

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

4

2 回答 2

9

我会说你有一个非常复杂的场景,我通常会尽量让事情比这更简单 - 但如果它适合你,我认为添加更多这样的信息是可以的。(这似乎是合理的,直到你到达IAlphaandIBeta位;没有这些接口,Alpha根本Beta不需要任何实现,调用者可以使用INode<IAlpha>andINode<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.ChildrenN

    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 开始,您可以声明INode<N>为协变N

    public interface INode<out N> : INode
    
于 2011-08-12T05:27:56.147 回答
0

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

于 2011-08-12T05:30:34.670 回答