MYTH #1: STRUCTS ARE LIGHTWEIGHT CLASSES
This myth comes in a variety of forms. Some people believe that value types can’t or
shouldn’t have methods or other significant behavior—they should be used as simple
data transfer types, with just public fields or simple properties. The DateTime type is a
good counterexample to this: it makes sense for it to be a value type, in terms of being
a fundamental unit like a number or a character, and it also makes sense for it to be
able to perform calculations based on its value. Looking at things from the other
direction, data transfer types should often be reference types anyway—the decision
should be based on the desired value or reference type semantics, not the simplicity of
the type.
Other people believe that value types are “lighter” than reference types in terms
of performance. The truth is that in some cases value types are more performant—
they don’t require garbage collection unless they’re boxed, don’t have the type
identification overhead, and don’t require dereferencing, for example. But in other
ways, reference types are more performant—parameter passing, assigning values to
variables, returning values, and similar operations only require 4 or 8 bytes to becopied (depending on whether you’re running the 32-bit or 64-bit CLR) rather than
copying all the data. Imagine if ArrayList were somehow a “pure” value type, and
passing an ArrayList expression to a method involved copying all its data! In almost
all cases, performance isn’t really determined by this sort of decision anyway. Bottlenecks are almost never where you think they’ll be, and before you make a design decision based on performance, you should measure the different options.
It’s worth noting that the combination of the two beliefs doesn’t work either. It
doesn’t matter how many methods a type has (whether it’s a class or a struct)—the
memory taken per instance isn’t affected. (There’s a cost in terms of the memory
taken up for the code itself, but that’s incurred once rather than for each instance.)
MYTH #2: REFERENCE TYPES LIVE ON THE HEAP; VALUE TYPES LIVE ON THE STACK
This one is often caused by laziness on the part of the person repeating it. The first
part is correct—an instance of a reference type is always created on the heap. It’s the
second part that causes problems. As I’ve already noted, a variable’s value lives wherever it’s declared, so if you have a class with an instance variable of type int, that variable’s value for any given object will always be where the rest of the data for the object
is—on the heap. Only local variables (variables declared within methods) and method
parameters live on the stack. In C# 2 and later, even some local variables don’t really
live on the stack, as you’ll see when we look at anonymous methods in chapter 5.
ARE THESE CONCEPTS RELEVANT NOW? It’s arguable that if you’re writing managed code, you should let the runtime worry about how memory is best used.
Indeed, the language specification makes no guarantees about what lives
where; a future runtime may be able to create some objects on the stack if it
knows it can get away with it, or the C# compiler could generate code that
hardly uses the stack at all.
The next myth is usually just a terminology issue.
MYTH #3: OBJECTS ARE PASSED BY REFERENCE IN C# BY DEFAULT
This is probably the most widely propagated myth. Again, the people who make this
claim often (though not always) know how C# actually behaves, but they don’t know
what “pass by reference” really means. Unfortunately, this is confusing for people who
do know what it means.
The formal definition of pass by reference is relatively complicated, involving l-values
and similar computer-science terminology, but the important thing is that if you pass a
variable by reference, the method you’re calling can change the value of the caller’s variable by changing its parameter value. Now, remember that the value of a reference
type variable is the reference, not the object itself. You can change the contents of the
object that a parameter refers to without the parameter itself being passed by reference. For instance, the following method changes the contents of the StringBuilder
object in question, but the caller’s expression will still refer to the same object as
before:
void AppendHello(StringBuilder builder)
{
builder.Append("hello");
}
When this method is called, the parameter value (a reference to a StringBuilder) is
passed by value. If you were to change the value of the builder variable within the
method—for example, with the statement builder = null;—that change wouldn’t be
seen by the caller, contrary to the myth.
It’s interesting to note that not only is the “by reference” bit of the myth inaccurate, but so is the “objects are passed” bit. Objects themselves are never passed, either
by reference or by value. When a reference type is involved, either the variable is
passed by reference or the value of the argument (the reference) is passed by value.
Aside from anything else, this answers the question of what happens when null is
used as a by-value argument—if objects were being passed around, that would cause
issues, as there wouldn’t be an object to pass! Instead, the null reference is passed by
value in the same way as any other reference would be.
If this quick explanation has left you bewildered, you might want to look at my article, “Parameter passing in C#,” (http://mng.bz/otVt), which goes into much more
detail.
These myths aren’t the only ones around. Boxing and unboxing come in for their
fair share of misunderstanding, which I’ll try to clear up next.
Reference: C# in Depth 3rd Edition by Jon Skeet