问题
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?
回答1:
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
回答2:
Why not simply use a type argument (that is an argument to the generic type) to determine the type of a child. Then INode would still have the same sematics but you wouldn't need to shadow at all And you do indeed have shadowing in the implementation casting to INode will result in the same issues you describe in your post
来源:https://stackoverflow.com/questions/7035781/shadowing-inherited-generic-interface-members-in-net-good-bad-or-ugly