Can structs contain fields of reference types? And if they can is this a bad practice?
Yes they can.
It depends.
Many hold the stance that a struct should be immutable, and in this case, holding a reference to an object could mean it isn't.
But it depends on the situation.
The reason you cannot have mutable structs is because of the behavoir of reference types. Read this article: http://www.yoda.arachsys.com/csharp/parameters.html
When you have a struct that contains an Object (anything that isn't a primitive like int or double) and you copy an instance of the struct, the Object inside isn't "deep" copied, because it is simply a reference (pointer) to a memory location containing the actual class. So if you copy a mutable struct that contains class instances, the copy will be referencing the same instances as the original (Hence bar's list being changed above).
If you absolutely have to have the struct be mutable, make any class instances inside readonly, or - this is the bad practice - try to ensure that you never make a copy of the struct.
Since this got a downvote, I'm trying to rewrite a little to see if it can become clearer. This question is old; but good! I recently also came across a few links that elaborate on this.
The point I'm trying to add is that if you do declare reference fields, you have to be able to reason outside of your own block: when someone uses your struct. The specific point I added was really only about declaring a readonly field of a struct; but in that case, the fields you have in your struct can change their results; and it's hard to reason about.
I came across this link, where the programmer declared a class containing a readonly
struct
field. The field in his class is a struct
--- it's a LinkedList<T>.Enumerator
-- and it broke because the field is readonly
--- his own class methods get a copy of the enumerator struct and the state is copied and not dynamic.
But, if you go ahead and fix his code by simply removing the readonly
from the struct field (which works); and then however, if you then decide to make your own class a struct
, now once again, a consumer of your struct cannot use it as a readonly field or else they in turn get bitten by the same problem. (And if this seems contrived because you won't have a readonly enumerator, you actually might if it supports Reset!)
So if it's not the clearest example, the point I'm making is that you can reason about your own implementation, but if you are a struct you need to also reason about consumers that copy your value and what they will get.
The example i found is linked below.
His class is not a struct
, but does contain the m_Enumerator field (and the programmer supposedly knows that it is a struct
).
It turns out that this class's methods get a copy of that value, and do not work. --- You can actually examine this block very carefully to understand that.
You can fix it by making the field not readonly
--- which is already pointing at confusing. But you can also fix it by declaring the field as the interface
type --- IEnumerator<int>
.
But, if you do fix it by leaving the field declared as the struct
and declare it not readonly
, and then choose to define your class as a struct
, then now if someone declares an instance of your struct
as a readonly
field in some class, again they lose!
E.G.:
public class Program
{
private struct EnumeratorWrapper : IEnumerator<int>
{
// Fails always --- the local methods read the readonly struct and get a copy
//private readonly LinkedList<int>.Enumerator m_Enumerator;
// Fixes one: --- locally, methods no longer get a copy;
// BUT if a consumer of THIS struct makes a readonly field, then again they will
// always get a copy of THIS, AND this contains a copy of this struct field!
private LinkedList<int>.Enumerator m_Enumerator;
// Fixes both!!
// Because this is not a value type, even a consumer of THIS struct making a
// readonly copy, always reads the memory pointer and not a value
//private IEnumerator<int> m_Enumerator;
public EnumeratorWrapper(LinkedList<int> linkedList)
=> m_Enumerator = linkedList.GetEnumerator();
public int Current
=> m_Enumerator.Current;
object System.Collections.IEnumerator.Current
=> Current;
public bool MoveNext()
=> m_Enumerator.MoveNext();
public void Reset()
=> ((System.Collections.IEnumerator) m_Enumerator).Reset();
public void Dispose()
=> m_Enumerator.Dispose();
}
private readonly LinkedList<int> l = new LinkedList<int>();
private readonly EnumeratorWrapper e;
public Program()
{
for (int i = 0; i < 10; ++i) {
l.AddLast(i);
}
e = new EnumeratorWrapper(l);
}
public static void Main()
{
Program p = new Program();
// This works --- a local copy every time
EnumeratorWrapper e = new EnumeratorWrapper(p.l);
while (e.MoveNext()) {
Console.WriteLine(e.Current);
}
// This fails if the struct cannot support living in a readonly field
while (p.e.MoveNext()) {
Console.WriteLine(p.e.Current);
}
Console.ReadKey();
}
}
If you declare a struct
with an interface
field, you won't know what you have in there, but yet you can actually reason more about what you get when you simply reference it! This is very interesting; but only because the language allows so many freedoms with a struct
: you need to start from something very simple; and add only what you can concretely reason about!
One more point is that the reference also says you should define the default values as reasonable; which is not possible with a reference field! If you inadvertently invoke the default constructor --- always possible with a struct
--- then you get a null reference.
One last note also. Many folks defend mutable structs, and large mutable structs. But if you peer in, you usually discover that they are simply scoping those objects in a way that allows them to finitely reason about the behaviors, and the structs do not leak into scopes where those invariants could change.
... Too many people begin explaining structs as "Just like a class but ... x, y, z, 1, 2, alpha, beta disco". It must be explained as a couple of readonly values; period; except now that you know something, you can begin to reason about adding something!
The example I came accros is here:
https://www.red-gate.com/simple-talk/blogs/why-enumerator-structs-are-a-really-bad-idea/
Yes, it is possible, and yes, it is usually a bad practice.
If you look at the .NET framework itself, you'll see virtually all structs contain primitive value types alone.
Sure thing and it's not bad practice to do so.
struct Example {
public readonly string Field1;
}
The readonly is not necessary but it is good practice to make struct's immutable.
Yes, they can. Is it a good idea? Well, that depends on the situation. Personally I rarely create my own structs in the first place... I would treat any new user-defined struct with a certain degree of scepticism. I'm not suggesting that it's always the wrong option, just that it needs more of a clear argument than a class.
It would be a bad idea for a struct to have a reference to a mutable object though... otherwise you can have two values which look independent but aren't:
MyValueType foo = ...;
MyValueType bar = foo; // Value type, hence copy...
foo.List.Add("x");
// Eek, bar's list has now changed too!
Mutable structs are evil. Immutable structs with references to mutable types are sneakily evil in different ways.