在.NET中隐藏继承的通用接口成员:好,坏或丑? [英] Shadowing Inherited Generic Interface Members in .NET: good, bad or ugly?
问题描述
我知道类实现中的影子成员会导致出现错误成员可以调用的情况,这取决于我如何投射我的实例,但是使用接口我没有看到这可能是一个问题,而且我发现我经常写这样的界面:
public interface INode
{
IEnumerable< INode>孩子们{get; }
}
public interface INode< N> :INode
其中N:INode< N>
{
new IEnumerable< N>孩子们{get; }
}
public interface IAlpha:INode< IAlpha>
{}
public interface IBeta:INode< IBeta>
{}
我的代码中只有 INode
,所以孩子也应该是 INode
。
在其他地方,我想知道具体的类型 - 在我的例子 IAlpha
& IBeta
接口我希望孩子的父类相同。
$ b 所以我实现了一个 NodeBase
类如下所示:
public abstract class NodeBase< N> :INode< N>
其中N:INode< N>
{
只读保护列表< N> _children = new List< N>();
public IEnumerable< N>儿童
{
得到{return _children.AsEnumerable(); }
}
IEnumerable< INode> INode.Children
{
get {return this.Children.Cast< INode>(); }
}
}
在实际执行中没有影子,只有在接口。
IAlpha
& IBeta
看起来像这样:
public class Alpha:NodeBase< Alpha> ,IAl
{
IEnumerable< IAlpha> INode< IAlpha> .Children
{
get {return this.Children.Cast< IAlpha>(); }
$ b public class Beta:NodeBase< Beta>,IBeta
{
IEnumerable< IBeta> INode< IBeta> .Children
{
get {return this.Children.Cast< IBeta>(); }
}
}
同样,在实现中没有阴影。 p>
我现在可以像这样访问这些类型:
var alpha = new Α();
var beta = new Beta();
var alphaAsIAlpha = alpha作为IAlpha;
var betaAsIBeta = beta为IBeta;
var alphaAsINode = alpha作为INode;
var betaAsINode = beta作为INode;
var alphaAsINodeAlpha = alpha作为INode< Alpha>;
var betaAsINodeBeta = beta作为INode< Beta>;
var alphaAsINodeIAlpha = alpha作为INode< IAlpha>;
var betaAsINodeIBeta = beta作为INode< IBeta>;
var alphaAsNodeBaseAlpha = alpha作为NodeBase< Alpha>;
var betaAsNodeBaseBeta = beta作为NodeBase< Beta>;
现在每个变量都有正确的强类型 Children
collection。
所以,我的问题很简单。界面成员使用这种模式的阴影是好的,坏的还是丑陋的?为什么?
我会说你已经有了一个非常复杂的场景,而且我通常会尝试使事情比这更简单 - 但如果它对你有用,我认为可以添加更多这样的信息。 (直到你看到 IAlpha
和 IBeta
位;没有这些接口, Alpha
和 Beta
根本不需要任何实现,调用者可以使用 INode< IAlpha>
和 INode< IBeta>
。 $ b
特别注意 IEnumerable< T>
有效地做了同样的事情 - 没有隐瞒一个泛型与另一个,诚然,但隐藏一个非泛型与泛型。
其他四点: <
AsEnumerable
c $ c> NodeBase 是毫无意义的;调用者仍然可以转换为 List< T>
。 Select(x => x)
(理论上 Skip(0)
might 工作,但它可以被优化掉; LINQ to Objects没有非常好的记录,可以保证操作符隐藏原始impl ementation。 选择
保证不会。实际上, Take(int.MaxValue)
也会起作用。)
public class Alpha:NodeBase< Alpha>,IAlb
{
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>
其中N:class,INode< N> //请注意类约束
{
...
IEnumerable< INode> INode.Children
{
get {return this.Children; }
}
}
从C#4开始,您可以在 N
中声明 INode< N>
是协变的:
public interface INode< out N> :INode
I know that shadowing members in class implementations can lead to situations where the "wrong" member can get called depending on how I have cast my instances, but with interfaces I don't see that this can be a problem and I find myself writing interfaces like this quite often:
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>
{ }
I have places in my code that only know about INode
so children should also be of type INode
.
In other places I want to know about the specific types - in the implementation of my example IAlpha
& IBeta
interfaces I want the children to be typed the same as their parent.
So I implement a NodeBase
class like so:
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>(); }
}
}
No shadowing in the actual implementation, only in the interfaces.
Specific instances of IAlpha
& IBeta
look like this:
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>(); }
}
}
Again, no shadowing in the implementations.
I can now access these types like so:
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>;
Each of these variables now have the correct, strongly-type Children
collection.
So, my questions are simple. Is the shadowing of interface members using this kind of pattern good, bad or ugly? And why?
I would say you've got yourself a pretty complicated scenario there, and I generally try to keep things simpler than that - but if it works for you, I think it's okay to add more information like this. (It seems reasonable until you get to the IAlpha
and IBeta
bit; without those interfaces, Alpha
and Beta
don't need any implementation at all, and callers can just use INode<IAlpha>
and INode<IBeta>
instead.
In particular, note that IEnumerable<T>
effectively does the same thing - not hiding one generic with another, admittedly, but hiding a non-generic with a generic.
Four other points:
Your call to
AsEnumerable
inNodeBase
is pointless; callers can still cast toList<T>
. If you want to prevent that, you can do something likeSelect(x => x)
. (In theorySkip(0)
might work, but it could be optimized away; LINQ to Objects isn't terribly well documented in terms of which operators are guaranteed to hide the original implementation.Select
is guaranteed not to. Realistically,Take(int.MaxValue)
would work too.)As of C# 4, your two "leaf" classes can be simplified due to covariance:
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; } } }
As of C# 4, your
NodeBase
implementation ofINode.Children
can be simplified if you're willing to restrictN
to be a reference type:public abstract class NodeBase<N> : INode<N> where N : class, INode<N> // Note the class constraint { ... IEnumerable<INode> INode.Children { get { return this.Children; } } }
As of C# 4, you can declare
INode<N>
to be covariant inN
:public interface INode<out N> : INode
这篇关于在.NET中隐藏继承的通用接口成员:好,坏或丑?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!