2011-02-04 8 views
9

IEnumerable<T>をクラスのプロパティとして公開すると、クラスのユーザーによって突然変異が起こる可能性はありますか?そうであれば、公開されたプロパティの型を保持しながら突然変異から保護する最善の方法はIEnumerable<T>です?IEnumerableをプロパティに公開するのは安全ですか?

+0

コレクションをプロパティとして使用する際には注意が必要です。このデザインガイドのリンクをMSDN から確認してください。https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/guidelines-for-collections –

答えて

14

これは返品内容によって異なります。変更可能な文字を返すと(List<string>)、クライアントはそれを実際にList<string>にキャストして変異させることができます。

あなたのデータを保護する方法は、あなたが始めなければならないものによって決まります。 IList<T>が始まるとすれば、ReadOnlyCollection<T>は良いラッパークラスです。効果的にイテレータでコレクションをラップ

public IEnumerable<string> Names 
{ 
    get { return names.Select(x => x); } 
} 

:あなたのクライアントはIList<T>またはICollection<T>を実装し、戻り値の恩恵を受けない場合

、あなたはいつものような何かを行うことができます。 (LINQを使ってソースを非表示にする方法はいろいろありますが、どのオペレータがソースを非表示にしていないかは記載されていませんが、Skip(0)を呼び出すとはMicrosoftの実装でソースを隠しますそのように文書化されています)

Select間違いなくソースを非表示にします。

+1

いつものように、Jon Skeetは良い答えを提供します。私はちょうど別のデータ収集の種類は、プログラムのセキュリティのために使用するためのものではないことを追加する必要があります。完全に信頼できる環境で動作させると、内部に隠されたものを見つけるためにオブジェクトに深く掘り下げることができます。実際にコレクションを変更から保護する必要がある場合は、アプリケーションセキュリティメカニズムを使用する必要があります。 –

1

コレクションを元のタイプにキャストバックすることができます。コレクションが変更可能な場合は、コレクションを変更することができます。

オリジナルの変更の可能性を回避する方法の1つは、リストのコピーです。

+0

クローンを作ることは可能ですが、それは本当に「最良の方法」ですか?たぶんそれは時々あるが時にはそうではない。 –

+0

大きなコレクションの場合、コピーを返すのは高価です。コピーのセマンティクスが本当に必要な場合を除き、私はJonSkeetのようなラッパーを推奨します。 – CodesInChaos

+0

@Al、@CodeInChaos - 公正なポイント...回答が修正されました。 – Oded

2

ユーザーがコレクションクラスにキャストし直すことができるため、公開している可能性があります。

collection.Select(x => x) 

と、これは新しいIEnumerableを私は基本的な接続でmonkeyingから受信者を防ぐために、イテレータでのIEnumerableをラップしないようにお勧めしているが、コレクション

+0

私はこれが好きです。コレクションが参照型である場合、要素のメンバーはまだ変更可能ですが、リストまたは配列にスラッシングして列挙型のカーディナリティと順序を変更しても、元のコレクションには影響しないことを理解してください。 – KeithS

0

にキャストすることはできません作成されます。プロパティの戻り値の型がIEnumerable<T>ある場合は、WrappedEnumerable<T>からIEnumerable<T>への型変換は構造をボックスと動作とパフォーマンスは、クラスのものと一致するだろう

public struct WrappedEnumerable<T> : IEnumerable<T> 
{ 
    IEnumerable<T> _dataSource; 
    public WrappedEnumerable(IEnumerable<T> dataSource) 
    { 
     _dataSource = dataSource; 
    } 
    public IEnumerator<T> GetEnumerator() 
    { 
     return _dataSource.GetEnumerator(); 
    } 
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
    { 
     return ((System.Collections.IEnumerable)_dataSource).GetEnumerator(); 
    } 
} 

:私の傾きは次のようにラッパーものを使用することです。しかし、プロパティが戻り値のタイプとしてWrappedEnumerable<T>と定義されていた場合、呼び出しコードが戻り値をタイプWrappedEnumerable<T>(おそらくvar myKeys = myCollection.Keys;のような結果の可能性が高い)に割り当てる場合にボクシングステップを保存することが可能です)、または単に "foreach"ループでプロパティを直接使用します。 GetEnumerator()によって返された列挙子が構造体である場合、それはどんな場合でもボックス化されなければならないことに注意してください。

クラスではなく構造体を使用することのパフォーマンス上の利点は、通常はごくわずかです。概念的には、構造体を使用すると、プロパティが新しいヒープオブジェクトインスタンスを作成しないという一般的な推奨に適合します。既存のヒープオブジェクトへの参照だけを含む新しい構造体インスタンスを作成することは非常に安価です。ここで定義されている構造体を使用することの最大の欠点は、呼び出しコードに返されるものの動作をロックし、単にIEnumerable<T>を返すと他のアプローチが可能になることです。

public struct FancyWrappedEnumerable<TItems,TEnumerator,TDataSource> : IEnumerable<TItems> where TEnumerator : IEnumerator<TItems> 
{ 
    TDataSource _dataSource; 
    Func<TDataSource,TEnumerator> _convertor; 
    public FancyWrappedEnumerable(TDataSource dataSource, Func<TDataSource, TEnumerator> convertor) 
    { 
     _dataSource = dataSource; 
     _convertor = convertor; 
    } 
    public TEnumerator GetEnumerator() 
    { 
     return _convertor(_dataSource); 
    } 
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
    { 
     return _convertor(_dataSource); 
    } 
} 

:つのタイプなどを使用した場合、いくつかのケースでは、任意のボクシングの必要性を排除し、C#とvb.net foreachループでダックタイピングの最適化を利用することが可能であること

も注意convertorデリゲートは静的デリゲートになる可能性があるため、実行時に(クラスの初期化以外の)ヒープオブジェクトの作成は必要ありません。このアプローチを使用して、List<int>から列挙子を返す場合は、戻り値の型はFancyWrappedEnumerable<int, List<int>.Enumerator, List>になります。おそらく呼び出し元がforeachループまたはvar宣言で直接プロパティを使用した場合はおそらく合理的ですが、呼び出し元がvarを使用できない方法で型の格納場所を宣言したいと思っていた場合は、

関連する問題