struct with string member?无效的默认实例可以接受?

时间:2013-03-18 04:05:57

标签: c# struct

这是结构的一个很好的候选者吗?

考虑这个不可变的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的对象 - 这是无效的路由代码。这与其他结构不同,例如PointBigInteger,其中支持字段包含默认实例的非常逻辑且有效的“零”。

除了不确保有效的路由代码之外,RoutingLength属性如果在默认实例上调用,则会抛出NullReferenceException

保持structclass相比有什么理由?

2 个答案:

答案 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)

隐藏字段结构可能使未初始化的实例表现得好像它们具有任何特定的期望值[对于给定类型的所有未初始化实例,默认值必须相同]。只需让每个属性访问器或方法检查类型是否包含系统默认值,如果是,则替换所需的默认值。

如果有人想要允许代码创建Routingnull的实例并且没有这样的实例,则认为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默认值,请使用一个类。如果想要一个有效的默认值,请使用结构。