这是结构的一个很好的候选者吗?
考虑这个不可变的struct示例,其中构造函数验证输入并将验证的数据存储为单个“路由代码”:
struct RoutingCode
{
private readonly string routingCode;
RoutingCode(string prefix, string service, string sender)
{
// Validate prefix, service, sender strings
// ...
if (isInvalid) throw ArgumentException();
//
this.routingCode = prefix + service + sender;
}
// Gets the validated routing code.
public string Routing
{
return this.routingCode;
}
public int RoutingLength
{
return this.routingCode.Length;
}
}
在我看来,这个简化示例是使用struct
代替class
的合适人选:
问题是所有结构都有一个隐式默认构造函数。在此示例中,默认构造函数RoutingCode()
将实例化Routing
属性返回null
的对象 - 这是无效的路由代码。这与其他结构不同,例如Point
或BigInteger
,其中支持字段包含默认实例的非常逻辑且有效的“零”。
除了不确保有效的路由代码之外,RoutingLength
属性如果在默认实例上调用,则会抛出NullReferenceException
。
保持struct
与class
相比有什么理由?
答案 0 :(得分:2)
您可以轻松解决默认值问题:
struct RoutingCode
{
private readonly string routingCode;
RoutingCode(string prefix, string service, string sender)
{
// Validate prefix, service, sender strings
// ...
if (isInvalid) throw ArgumentException();
this.routingCode = prefix + service + sender;
}
public string IsValid
{
get { return this.routingCode != null; }
}
// Gets the validated routing code.
public string Routing
{
get { return this.routingCode; }
}
public int RoutingLength
{
get { return this.routingCode == null ? 0 : this.routingCode.Length; }
}
}
好的,现在没有任何属性抛出异常,你可以判断该值是否无效。手头的问题是这是否是结构的良好候选者。你是对的,它是(1)不可变的,(2)小的,(3)逻辑上是一个值。如果您愿意接受可以表示无效值的事实,那么这可能是结构的一个很好的候选者。
一个更好的问题是:有没有一个很好的理由不成为一个班级?而不是寻找反对使它成为一个结构,而是寻找反对使它成为一个类。
答案 1 :(得分:0)
隐藏字段结构可能使未初始化的实例表现得好像它们具有任何特定的期望值[对于给定类型的所有未初始化实例,默认值必须相同]。只需让每个属性访问器或方法检查类型是否包含系统默认值,如果是,则替换所需的默认值。
如果有人想要允许代码创建Routing
为null
的实例并且没有这样的实例,则认为Routing
是默认值,可以让类型声明为静态字符串等于新的GUID(或其他不会出现在其他地方的东西),然后修改构造函数和get
方法:
static string stringRepresentingNull = Guid.NewGuid().ToString();
RoutingCode(string wholeString)
{
if (wholeString == null)
wholeString = stringRepresentingNull;
this.routingCode = wholeString;
}
public string Routing
{
get {
if (this.routingCode == null)
return "12-3456-789"; // Default routing string
else if (Object.ReferenceEquals(routingCode,stringRepresentingNull)
return null;
else
return routingCode;
}
请注意,即使外部代码以某种方式设法猜测与该类型相关联的GUID,外部代码也无法生成ReferenceEquals
匹配stringRepresentingNull
的字符串。
类似地,如果想要拥有默认值为123的int
属性,则可以存储backingField=(desiredValue ^ 123)
并使属性getter yield backingField ^ 123
。
类型应该是结构还是类的问题主要归结为是否希望将类型的默认值表现为null
或作为有效值。如果想要一个null默认值,请使用一个类。如果想要一个有效的默认值,请使用结构。