コンパイル時に割り当てを禁止することが目的の場合は、コンストラクターの割り当てとプライベートセッターに固執する必要があります。ただし、これには多くの欠点があります。新しいメンバーの初期化やxmlの非セラリゼーションなどを使用することはできません。
私はこのようなものを提案します:
public class IssuerRecord
{
public string PropA { get; set; }
public IList<IssuerRecord> Subrecords { get; set; }
}
public class ImmutableIssuerRecord
{
public ImmutableIssuerRecord(IssuerRecord record)
{
PropA = record.PropA;
Subrecords = record.Subrecords.Select(r => new ImmutableIssuerRecord(r));
}
public string PropA { get; private set; }
// lacks Count and this[int] but it's IReadOnlyList<T> is coming in 4.5.
public IEnumerable<ImmutableIssuerRecord> Subrecords { get; private set; }
// you may want to get a mutable copy again at some point.
public IssuerRecord GetMutableCopy()
{
var copy = new IssuerRecord
{
PropA = PropA,
Subrecords = new List<IssuerRecord>(Subrecords.Select(r => r.GetMutableCopy()))
};
return copy;
}
}
ここのIssuerRecordは、はるかに説明的で便利です。他の場所に渡すと、不変バージョンを簡単に作成できます。不変で動作するコードは読み取り専用ロジックを備えている必要があるため、IssuerRecordと同じタイプであるかどうかは気にしないでください。オブジェクトをラップするのではなく、他の場所で変更される可能性があるため、各フィールドのコピーを作成しますが、特にシーケンシャル同期呼び出しでは必要ない場合があります。ただし、完全に不変のコピーを「後で」コレクションに保存する方が安全です。ただし、一部のコードで変更を禁止したいが、オブジェクトの状態の更新を受信できるようにしたい場合は、アプリケーションのラッパーになる可能性があります。
var record = new IssuerRecord { PropA = "aa" };
if(!Verify(new ImmutableIssuerRecord(record))) return false;
C ++の用語で考えると、ImmutableIssuerRecordsは「IssuerRecordconst」と見なすことができます。ただし、不変オブジェクトが所有するオブジェクトを保護するために細心の注意を払う必要があります。そのため、すべての子のコピーを作成することをお勧めします(サブレコードの例)。
ImmutableIssuerRecord.SubrecorsはここではIEnumerableであり、Countとthis []がありませんが、IReadOnlyListは4.5で提供され、必要に応じてドキュメントからコピーできます(後で簡単に移行できます)。
Freezableなどの他のアプローチもあります。
public class IssuerRecord
{
private bool isFrozen = false;
private string propA;
public string PropA
{
get { return propA; }
set
{
if( isFrozen ) throw new NotSupportedOperationException();
propA = value;
}
}
public void Freeze() { isFrozen = true; }
}
これにより、コードが再び読みにくくなり、コンパイル時の保護が提供されなくなります。ただし、通常どおりオブジェクトを作成し、準備ができたらフリーズすることができます。
ビルダーパターンも考慮すべき点ですが、私の観点からすると、「サービス」コードが多すぎます。