If your intent is prohibit assignments during compilation time, than you have to stick to constructor assignments and private setters. However it has numerous disadvantages - you are not able to use new member initialization, nor xml deseralization and etc.
I would suggest something like this:
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 here is much more descriptive and useful. When you pass it somewhere else you can easily create immutable version. Code that works on immutable should have read-only logic, so it should not really care if it is the same type as IssuerRecord. I create a copy of each field instead of just wrapping the object because it may still be changed somewhere else, but it may not be necessary especially for sequential sync calls. However it is safer to store full immutable copy to some collection "for later". It may be a wrapper though for applications when you want some code to prohibit modifications but still have ability to receive updates to the object state.
var record = new IssuerRecord { PropA = "aa" };
if(!Verify(new ImmutableIssuerRecord(record))) return false;
if you think in C++ terms, you can see ImmutableIssuerRecords as "IssuerRecord const". You have to take extracare though to protect objects that are owned by your immutable object that's why I suggest to create a copy for all children (Subrecords example).
ImmutableIssuerRecord.Subrecors is IEnumerable here and lacks Count and this[], but IReadOnlyList is coming in 4.5 and you can copy it from docs if wanted (and make it easy to migrate later).
there are other approaches as well, such as 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; }
}
which makes code less readable again, and does not provide compile-time protection. but you can create objects as normal and then freeze them after they are ready.
Builder pattern is also something to consider but it adds too much of "service" code from my point of view.