Search code examples
c#value-typereference-type

When would a value type contain a reference type?


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)?


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