It is important to realise that everything in C# is passed by value, unless you specify ref
or out
in the signature.
What makes value types (and hence struct
s) different from reference types is that a value type is accessed directly, while a reference type is accessed via its reference. If you pass a reference type into a method, its reference, not the value itself, is passed by value.
To illustrate, imagine we have a class PointClass
and a struct PointStruct
, defined analogously (omitting irrelevant details):
struct PointStruct { public int x, y; }
class PointClass { public int x, y; }
And we have a method SomeMethod
that takes these two types by value:
static void ExampleMethod(PointClass apc, PointStruct aps) { … }
If we now create two objects and call the method:
var pc = new PointClass(1, 1);
var ps = new PointStruct(1, 1);
ExampleMethod(pc, ps);
…?we can visualise this with the following diagram:
Since pc
is a reference, it doesn’t contain the value in itself; rather, it references an (unnamed) value somewhere else in memory. This is visualised by the dashed border and the arrow.
But: for both pc
and ps
, the actual variable is copied when calling the method.
What happens if ExampleMethod
reassigns the argument variables internally? Let’s check:
static void ExampleMethod(PointClass apc, PointStruct aps); {
apc = new PointClass(2, 2);
aps = new PointStruct(2, 2);
}
Output of pc
and ps
after calling the method:
pc: {x: 1, y: 1}
ps: {x: 1, y: 1}
→?ExampleMethod
changed a copy of the values, and the original values are unaffected.
This, fundamentally, is what “pass by value” means.
There’s still a difference between reference and value types, and that comes into play when modifying members of the value, not the variable itself. This is the part that trips people up when they are confronted with the fact that reference types are passed by value. Consider a different ExampleMethod
.
static void ExampleMethod(PointClass apc, PointStruct aps) {
apc.x = 2;
aps.x = 2;
}
Now we observe the following result after calling the method:
pc: {x: 2, y: 1}
ps: {x: 1, y: 1}
→ The reference object was changed, whereas the value object wasn’t. The diagram above shows why that is: for the reference object, even though pc
was copied, the actual value that both pc
and apc
reference remains identical, and we can modify that via apc
. As for ps
, we copied the actual value itself into aps
; the original value cannot be touched by ExampleMethod
.