.NETで不変配列は可能ですか?
-
03-07-2019 - |
質問
何らかの方法でSystem.Array
を不変としてマークすることは可能ですか? public-get / private-setの背後に配置すると、再割り当てと再割り当てが必要になるため、追加できませんが、消費者は引き続き任意の添え字を設定できます。
public class Immy
{
public string[] { get; private set; }
}
readonly
キーワードがうまくいくかもしれないと思ったが、そのような運はない。
解決
ReadOnlyCollection<T>
がおそらく探しているものです。 Add()
メソッドはありません。
他のヒント
フレームワーク設計ガイドラインでは、配列のコピーを返すことが推奨されています。そうすれば、消費者は配列のアイテムを変更できません。
// bad code
// could still do Path.InvalidPathChars[0] = 'A';
public sealed class Path {
public static readonly char[] InvalidPathChars =
{ '\"', '<', '>', '|' };
}
これらの方が優れています:
public static ReadOnlyCollection<char> GetInvalidPathChars(){
return Array.AsReadOnly(InvalidPathChars);
}
public static char[] GetInvalidPathChars(){
return (char[])InvalidPathChars.Clone();
}
例は本から直接です。
ベースクラスライブラリで現在使用可能な不変のコレクション (現在プレビュー中)。
Array.AsReadOnlyメソッドを使用して戻ることができます。
ベストプラクティスはIList <!> lt; T <!> gt;を使用することだと思いますこの正確な理由から、パブリックAPIの配列ではありません。 readonly は、メンバー変数がコンストラクターの外部に設定されるのを防ぎますが、あなたが発見したように、人々が配列内の要素を割り当てることを防ぎません。
やや有害と考えられる配列を参照してください。 で詳細をご覧ください。
編集:配列を読み取り専用にすることはできませんが、@ shahkalpeshが指摘しているように、Array.AsReadOnly()を介して読み取り専用のIList実装に変換できます。
.NETは、最も単純で最も伝統的なユースケース以外のすべての場合、配列から遠ざかる傾向があります。それ以外には、さまざまな列挙型/コレクション実装があります。
データのセットを不変としてマークする場合、従来の配列が提供する機能を超えています。 .NETは同等の機能を提供しますが、技術的には配列の形式ではありません。配列から不変のコレクションを取得するには、 Array.AsReadOnly<T>
:
var mutable = new[]
{
'a', 'A',
'b', 'B',
'c', 'C',
};
var immutable = Array.AsReadOnly(mutable);
immutable
は、 ReadOnlyCollection<char>
インスタンス。より一般的な使用例として、 "> ReadOnlyCollection<T>
「MSDN:System.Collections.Generic.IList IList<T>
実装。
var immutable = new ReadOnlyCollection<char>(new List<char>(mutable));
一般的な実装でなければならないことに注意してください。 plain old <= > は機能しません。つまり、 IList
。これにより、 <=> は、従来の配列では通常アクセスできない一般的な実装へのアクセスをすばやく取得する手段として。
<=> を使用すると、不変配列に期待されるすべての機能にアクセスできます。
// Note that .NET favors Count over Length; all but traditional arrays use Count:
for (var i = 0; i < immutable.Count; i++)
{
// this[] { get } is present, as ReadOnlyCollection<T> implements IList<T>:
var element = immutable[i]; // Works
// this[] { set } has to be present, as it is required by IList<T>, but it
// will throw a NotSupportedException:
immutable[i] = element; // Exception!
}
// ReadOnlyCollection<T> implements IEnumerable<T>, of course:
foreach (var character in immutable)
{
}
// LINQ works fine; idem
var lowercase =
from c in immutable
where c >= 'a' && c <= 'z'
select c;
// You can always evaluate IEnumerable<T> implementations to arrays with LINQ:
var mutableCopy = immutable.ToArray();
// mutableCopy is: new[] { 'a', 'A', 'b', 'B', 'c', 'C' }
var lowercaseArray = lowercase.ToArray();
// lowercaseArray is: new[] { 'a', 'b', 'c' }
追加する唯一のことは、配列が可変性を暗示することです。関数から配列を返すとき、クライアントプログラマーに物事を変更できる/すべきであると提案しています。
Mattの答えに加えて、IListは配列への完全な抽象インターフェースであるため、追加、削除などが可能です。Lippertが不変が必要なIEnumerableの代替としてそれを提案するように見える理由はわかりません。 (編集: IList実装は、そうした種類のものが好きな場合、これらの変更メソッドに対して例外をスローできるためです。)
もう1つ注意すべき点は、リスト上の項目も変更可能な状態になっている可能性があることです。呼び出し側にそのような状態を変更させたくない場合、いくつかのオプションがあります:
リストの項目が不変であることを確認します(例のように、文字列は不変です)。
すべてのディープクローンを返すため、その場合は配列を使用できます。
アイテムへの読み取り専用アクセスを提供するインターフェースを返します:
interface IImmutable
{
public string ValuableCustomerData { get; }
}
class Mutable, IImmutable
{
public string ValuableCustomerData { get; set; }
}
public class Immy
{
private List<Mutable> _mutableList = new List<Mutable>();
public IEnumerable<IImmutable> ImmutableItems
{
get { return _mutableList.Cast<IMutable>(); }
}
}
IImmutableインターフェースからアクセス可能なすべての値は、それ自体が不変(文字列など)であるか、オンザフライで作成するコピーでなければならないことに注意してください。
できる限り最善の方法は、既存のコレクションを拡張して独自のコレクションを構築することです。大きな問題は、すべての呼び出しが新しいコレクションを返す必要があるため、既存のすべてのコレクションタイプとは異なる方法で動作する必要があることです。
同様の私の回答をチェックアウトすることをお勧めしますコレクションをオブジェクトに公開するためのアイデアについての質問。