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)?
A Value Type holds the data within its own memory allocation and a Reference Type contains a pointer to another memory location that holds the real data. Reference Type variables are stored in the heap while Value Type variables are stored in the stack.
Unlike value types, a reference type doesn't store its value directly. Instead, it stores the address where the value is being stored. In other words, a reference type contains a pointer to another memory location that holds the data.
All fundamental data types, Boolean, Date, structs, and enums are examples of value types. Examples of reference types include: strings, arrays, objects of classes, etc.
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.
}
There are two primary useful scenarios for a struct holding a class-type field:
(*) 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With