0

Is this a good candidate for a struct?

Consider this immutable struct example where the constructor validates input and stores the validated data as a single "routing code":

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;
  }
}

This simplified example appears to me to be a good candidate for using a struct instead of a class:

  • It is immutable.
  • It represents a singular value.
  • The instance size is small.

The problem is that all structs have an implicit default constructor. In this example, the default constructor RoutingCode() would instantiate an object where the Routing property returns null—which is an invalid routing code. This is a different from other structs such as Point or BigInteger where the backing fields contain a very logical and valid "zero" for default instances.

In addition to not ensuring a valid routing code, the RoutingLength property throws a NullReferenceException if called on a default instance.

What are the arguments for keeping this a struct versus making it a class?

3
  • I am seeing stackoverflow.com/q/13386719/733805 as substantially duplicate, and the accepted answer makes a good case to use a class. All input is appreciated. Commented Mar 18, 2013 at 4:18
  • I agree with your feeling concerning the duplicate use case however. However, I do not totally agree with the conclusion since your use case is a very good for a struct. In the end, you have to weigh up pros and cons... Commented Mar 18, 2013 at 4:26
  • @bigge Thanks. I think "A valid default instance" argues against my initial desire to use struct. As string is a reference type, the struct here doesn't really do much in the way of efficiency anyway. Commented Mar 18, 2013 at 4:38

2 Answers 2

2

You can easily solve your default value problems:

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; }
  }
}

OK, now none of the properties throw exceptions and you have a way to tell if the value is invalid. The question at hand is whether or not this is a good candidate for a struct. You are right that it is (1) immutable, (2) small, and (3) logically a value. If you're willing to live with the fact that you can represent an invalid value, then this is probably a good candidate for a struct.

A better question though is: is there a good reason for this not to be a class? Rather than looking for objections to making it a struct, look for objections to making it a class.

Sign up to request clarification or add additional context in comments.

6 Comments

In reply to, "Rather than looking for objections to making it a struct, look for objections to making it a class." Good advice, yet exploring the converse is helping highlight the differences between the two, which is useful.
I think (making an assumption or two) what Eric is saying is "why are you even contemplating changing it to a struct?" And if he's not, I would: what circumstances led you to look at this code and say "this should be a struct"?
@KevinP.Rice: I definitely agree with you that having all values be valid values is an excellent property for a struct to have. This is particularly important for structs that have some sort of "security" purpose; if you have a struct representing a "token" that grants some right, you'd better be sure that ability to construct the default value of the struct does not open a security hole.
@Eric If we add string as a struct member , it may possible that size of struct is dynamic because string may get changed during program execution so invalidated 3rd property of being small. Is it true?
@Saurabh: Strings are reference types, not value types, so a string field is always the size of a reference: 32 bits on 32 bit machines, 64 bits on 64 bit machines. The thing referred to might be huge, but that hardly matters. The size of a struct doesn't ever vary. (That is, the size can vary from machine to machine, but in a given instance of the program, every instance of a struct is the same size.)
|
0

It's possible for a hidden-field struct to have uninitialized instances behave as though they holds any particular desired value [the default value must be the same for all uninitialized instances of a given type]. Simply have each property accessor or method check whether the type holds the system default value and, if so, substitute the desired default value instead.

If one wanted to allow code to create an instance where Routing was null and not have such an instance assume that Routing was the default value, one could have the type declare a static string equal to a new GUID (or something else which isn't going to appear elsewhere), and then modify the constructor and get methods:

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;
}

Note that even if outside code somehow manages to guess the GUID that is associated with the type, there's no way for outside code to generate a string where ReferenceEquals will match stringRepresentingNull.

On a similar vein, if one wanted to have an int property with a default value of 123, one could store backingField=(desiredValue ^ 123) and have the property getter yield backingField ^ 123.

The question of whether the type should be a struct or a class largely boils down to one of whether one wants to have the default value of the type behave as a null, or as a valid value. If one wants a null default, use a class. If one wants a valid default, use a struct.

2 Comments

Excellent contribution and ideas. In your last sentences, did you transpose class/struct? Also, I'm not quite following the purpose of ^ 123. Is that obfuscation for security?
@KevinP.Rice: The purpose of ^123 is to provide a simple reversible transform so that the default field value of zero will read as 123, writing 123 will store the default (zero) value to the field, and writing any other value foo will store to the field a value which, when read, will be transformed back into foo. One could have used a function which would turn 0 into 123 and 123 into 0 while leaving other values unaffected, but unconditionally using xor is quicker and easier.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.