Question

I understand that the decision to use a value type over a reference type should be based on the semantics, not performance. I do not understand why value types can legally contain reference type members? This is for a couple reasons:

For one, we should not build a struct to require a constructor.

public struct MyStruct
{
    public Person p;
    // public Person p = new Person(); // error: cannot have instance field initializers in structs

    MyStruct(Person p)
    {
        p = new Person();
    }
}

Second, because of value type semantics:

MyStruct someVariable;
someVariable.p.Age = 2; // NullReferenceException

The compiler does not allow me to initialize Person at the declaration. I have to move this off to the constructor, rely on the caller, or expect a NullReferenceException. None of these situations are ideal.

Does the .NET Framework have any examples of reference types within value types? When should we do this (if ever)?

Was it helpful?

Solution

Instances of a value type never contain instances of a reference type. The reference-typed object is somewhere on the managed heap, and the value-typed object may contain a reference to the object. Such a reference has a fixed size. It is perfectly common to do this — for example every time you use a string inside a struct.

But yes, you cannot guarantee initialization of a reference-typed field in a struct because you cannot define a parameter-less constructor (nor can you guarantee it ever gets called, if you define it in a language other than C#).

You say you should "not build a struct to require a constructor". I say otherwise. Since value-types should almost always be immutable, you must use a constructor (quite possibly via a factory to a private constructor). Otherwise it will never have any interesting contents.

Use the constructor. The constructor is fine.

If you don't want to pass in an instance of Person to initialize p, you could use lazy initialization via a property. (Because obviously the public field p was just for demonstration, right? Right?)

public struct MyStruct
{
    public MyStruct(Person p)
    {
        this.p = p;
    }

    private Person p;

    public Person Person
    {
        get
        {
            if (p == null)
            {
                p = new Person(…); // see comment below about struct immutability
            }
            return p;
        }
    }

    // ^ in most other cases, this would be a typical use case for Lazy<T>;
    //   but due to structs' default constructor, we *always* need the null check.
}

OTHER TIPS

There are two primary useful scenarios for a struct holding a class-type field:

  1. The struct holds a possibly-mutable reference to an immutable object (`String` being by far the most common). An reference to an immutable object will behave as a cross between a nullable value type and a normal value type; it doesn't have the "Value" and "HasValue" properties of the former, but it will have null as a possible (and default) value. Note that if the field is accessed through a property, that property may return a non-null default when the field is null, but should not modify the field itself.
  2. The struct holds an "immutable" reference to a possibly-mutable object and serves to wrap the object or its contents. `List.Enumerator` is probably the most common struct using this pattern. Having struct fields pretend to be immutable is something of a dodgy construct(*), but in some contexts it can work out pretty well. In most instances where this pattern is applied, the behavior of a struct will be essentially like that of a class, except that performance will be better(**).

(*) The statement structVar = new structType(whatever); will create a new instance of structType, pass it to the constructor, and then mutate structVar by copying all public and private fields from that new instance into structVar; once that is done, the new instance will be discarded. Consequently, all struct fields are mutable, even if they "pretend" to be otherwise; pretending they are immutable can be dodgy unless one knows that the way structVar = new structType(whatever); is actually implemented will never pose a problem.

(**) Structs will perform better in some circumstances; classes will perform better in others. Generally, so-called "immutable" structs are chosen over classes in situations where they are expected to perform better, and where the corner cases where their semantics would differ from those of classes are not expected to pose problems.

Some people like to pretend that structs are like classes, but more efficient, and dislike using structs in ways that take advantage of the fact that they're not classes. Such people would probably only be inclined toward using scenario (2) above. Scenario #1 can be very useful with mutable structs, especially with types like String which behave essentially as values.

I wanted to add to Marc's answer, but I had too much to say for a comment.

If you look at the C# specifications, it says of struct constructors:

Struct constructors are invoked with the new operator, but that does not imply that memory is being allocated. Instead of dynamically allocating an object and returning a reference to it, a struct constructor simply returns the struct value itself (typically in a temporary location on the stack), and this value is then copied as necessary.

(You can find a copy of the spec under
C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC#\Specifications\1033)

So, a struct constructor is inherently different than a class constructor.

In addition to this, structs are expected to be copied by value, and thus:

With structs, the variables each have their own copy of the data, and it is not possible for operations on one to affect the other.

Any time I've seen a reference type in a struct, it has been a string. This works because strings are immutable. I am guessing your Person object is not immutable and can introduce very odd and severe bugs because of the divergence from the expected behavior of a struct.

That being said, the errors you're seeing with the constructor of your struct may be that you have a public field p with the same name as your parameter p and not referring to the struct's p as this.p, or that you're missing the keyword struct.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top