在.NET中隐藏继承的通用接口成员:好、坏还是难看
本文关键字:成员 接口 隐藏 NET 继承 | 更新日期: 2023-09-27 18:05:13
我知道在类实现中隐藏成员可能会导致调用"错误"成员的情况,这取决于我如何转换实例,但对于接口,我不认为这会是一个问题,我发现自己经常编写这样的接口:
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
&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>(); }
}
}
在实际实现中没有阴影,只有在接口中。
CCD_ 6&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
集合。
所以,我的问题很简单。使用这种模式的接口成员的阴影是好的、坏的还是难看的?为什么?
我想说你有一个非常复杂的场景,我通常会尝试让事情变得更简单,但如果它对你有效,我认为可以添加更多这样的信息。(在使用IAlpha
和IBeta
位之前,这似乎是合理的;如果没有这些接口,Alpha
和Beta
根本不需要任何实现,调用方只需使用INode<IAlpha>
和INode<IBeta>
即可。
特别要注意的是,IEnumerable<T>
有效地做了同样的事情——诚然,不是用另一个泛型隐藏一个泛型,而是用泛型隐藏非泛型。
其他四点:
-
您在
NodeBase
中呼叫AsEnumerable
毫无意义;呼叫者仍然可以转换为CCD_ 18。如果你想防止这种情况发生,你可以做一些类似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开始,如果您愿意将
N
限制为引用类型:,则INode.Children
的NodeBase
实现可以简化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<N>
是协变的public interface INode<out N> : INode
为什么不简单地使用类型参数(泛型类型的参数(来确定子类型。那么INode仍然会有相同的信号,但你根本不需要阴影而且在实现中确实存在阴影,将转换为INode将导致与您在后中描述的问题相同的问题