在.NET中隐藏继承的通用接口成员:好,坏或丑? [英] Shadowing Inherited Generic Interface Members in .NET: good, bad or ugly?

查看:82
本文介绍了在.NET中隐藏继承的通用接口成员:好,坏或丑?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我知道类实现中的影子成员会导致出现错误成员可以调用的情况,这取决于我如何投射我的实例,但是使用接口我没有看到这可能是一个问题,而且我发现我经常写这样的界面:

  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 in NodeBase is pointless; callers can still cast to List<T>. If you want to prevent that, you can do something like Select(x => x). (In theory Skip(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 of INode.Children can be simplified if you're willing to restrict N 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 in N:

    public interface INode<out N> : INode
    

这篇关于在.NET中隐藏继承的通用接口成员:好,坏或丑?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆