如果您的意图是在编译期间禁止分配,那么您必须坚持构造函数分配和私有设置器。但是它有很多缺点——你不能使用新成员初始化,也不能使用 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 视为“IssuerRecord const”。您必须格外小心,以保护您的不可变对象拥有的对象,这就是为什么我建议为所有孩子创建一个副本(子记录示例)。
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; }
}
这使代码的可读性再次降低,并且不提供编译时保护。但是您可以照常创建对象,然后在它们准备好后冻结它们。
构建器模式也是需要考虑的,但从我的角度来看,它添加了太多的“服务”代码。