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
IAlphaandIBetabit; without those interfaces,AlphaandBetadon’t need any implementation at all, and callers can just useINode<IAlpha>andINode<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
AsEnumerableinNodeBaseis 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.Selectis guaranteed not to. Realistically,Take(int.MaxValue)would work too.)As of C# 4, your two “leaf” classes can be simplified due to covariance:
As of C# 4, your
NodeBaseimplementation ofINode.Childrencan be simplified if you’re willing to restrictNto be a reference type:As of C# 4, you can declare
INode<N>to be covariant inN: