9

クラス実装でメンバーをシャドウイングすると、インスタンスのキャスト方法によっては「間違った」メンバーが呼び出される可能性があることを知っていますが、インターフェイスでは、これが問題になることはなく、インターフェイスを作成していることに気付きます。このように頻繁に:

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>(); }
    }
}

実際の実装ではシャドウイングはなく、インターフェイスのみです。

IAlpha&の特定のインスタンスは次の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コレクションが含まれるようになりました。

だから、私の質問は簡単です。この種のパターンを使用したインターフェイスメンバーのシャドウイングは、良いですか、悪いですか、それとも醜いですか?なぜ?

4

2 に答える 2

9

あなたはかなり複雑なシナリオを持っていると思います.私は通常、それよりも物事を単純にしようとしています. IAlpha( andビットにたどり着くまでは合理的に思えますIBeta。これらのインターフェイスがなくても、実装はまったく必要なく、呼び出し元は代わりにandを使用できます。AlphaBetaINode<IAlpha>INode<IBeta>

特に、 は効果的に同じことを行うことに注意してくださいIEnumerable<T>。確かに、あるジェネリックを別のジェネリックで隠すのではなく、ジェネリックで非ジェネリックを隠します。

その他の 4 つのポイント:

  • AsEnumerableinへの呼び出しNodeBaseは無意味です。発信者は引き続き にキャストできList<T>ます。それを防ぎたい場合は、次のようなことができますSelect(x => x)。(理論的には機能Skip(0) するかもしれませんが、最適化することはできます。LINQ to Objects は、どの演算子が元の実装を非表示にすることが保証されているかという点で十分に文書化されてSelectいません。そうでないことが保証されています。現実的にTake(int.MaxValue)は、あまりにも機能します。)

  • C# 4 以降、共分散により、2 つの「リーフ」クラスを単純化できます。

    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 の時点で、参照型に制限したい場合NodeBaseは、 の実装をINode.Children簡素化できます。N

    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 以降では、次のINode<N>ように共変であると宣言できNます。

    public interface INode<out N> : INode
    
于 2011-08-12T05:27:56.147 に答える
0

単純に型引数 (ジェネリック型への引数) を使用して、子の型を決定しないのはなぜですか。その後、INodeは同じセマティクスを持ちますが、シャドウする必要はまったくありません.INodeにキャストする実装で実際にシャドウイングを行うと、投稿で説明したのと同じ問題が発生します

于 2011-08-12T05:30:34.670 に答える