Are structs ‘pass-by-value’?

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 structs) 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:

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.

Leave a Comment