C# Copy by Value vs Copy by Reference
last modified May 16, 2025
This tutorial explains how C# handles copy by value and copy by reference, affecting variable assignment and parameter passing. Understanding these concepts is crucial for writing correct and efficient C# code, as it determines how data is shared or isolated between variables and methods.
Value Types vs Reference Types
C# types are split into value types and reference types, which dictate how data is copied, stored, and managed in memory.
- Value types include
int,double,bool,struct,enum, and other simple types. They are stored on the stack and contain the actual data. - Reference types include
class,array,string,delegate, andobject. They are stored on the heap, and variables hold a reference (pointer) to the data.
| Characteristic | Value Types | Reference Types |
|---|---|---|
| Storage | Stack (direct data) | Heap (reference to data) |
| Copy Behavior | Copy by value (independent copy) | Copy by reference (shared object) |
| Examples | int, double, struct |
class, array, string |
When you assign a value type variable to another, a new copy of the data is created. For reference types, only the reference is copied, so both variables point to the same object in memory.
Copy by Value Example
Value types create an independent copy when assigned, so changes to one variable don't affect the other. This is true for both built-in types and user-defined structs.
int a = 10;
int b = a; // Copy by value
Console.WriteLine($"Original: a = {a}, b = {b}");
b = 20; // Doesn't affect a
Console.WriteLine($"After change: a = {a}, b = {b}");
// Structs are value types
Point p1 = new (1, 2);
Point p2 = p1; // Copy by value
p2.X = 10; // Doesn't affect p1
Console.WriteLine($"p1 = ({p1.X}, {p1.Y}), p2 = ({p2.X}, {p2.Y})");
struct Point(int x, int y)
{
public int X = x;
public int Y = y;
}
The example shows value types (int, struct) creating
independent copies. Modifying one does not affect the other.
$ dotnet run Original: a = 10, b = 10 After change: a = 10, b = 20 p1 = (1, 2), p2 = (10, 2)
Copying Arrays of Value Types
Arrays themselves are reference types, even if their elements are value types. Assigning one array to another copies the reference, not the elements.
int[] arr1 = { 1, 2, 3 };
int[] arr2 = arr1; // arr2 references the same array as arr1
arr2[0] = 99;
Console.WriteLine($"arr1[0] = {arr1[0]}, arr2[0] = {arr2[0]}");
This shows that modifying arr2 also affects arr1,
because they reference the same array. The output will be:
$ dotnet run arr1[0] = 99, arr2[0] = 99
To create a true copy of the array, use Array.Copy or
Clone:
int[] arr1 = { 1, 2, 3 };
int[] arr2 = (int[])arr1.Clone();
arr2[0] = 123;
Console.WriteLine($"arr1[0] = {arr1[0]}, arr2[0] = {arr2[0]}");
This creates a new array with the same elements as arr1. Changes
to arr2 do not affect arr1. The Clone
method creates a shallow copy of the array, meaning it copies the elements
but not the objects they reference. For reference types, a deep copy is
needed to create a new instance of the objects in the array.
Strings: Reference Type with Immutable Behavior
Strings in C# are reference types, but they are immutable. This means that once a string object is created, it cannot be changed. Any operation that appears to modify a string actually creates a new string object in memory. As a result, assigning one string variable to another copies the reference, but changing one variable does not affect the other.
string s1 = "hello";
string s2 = s1; // Copy by reference (but strings are immutable)
Console.WriteLine($"Original: s1 = {s1}, s2 = {s2}");
s2 = "world"; // s1 is not affected
Console.WriteLine($"After change: s1 = {s1}, s2 = {s2}");
In this example, s1 and s2 initially reference the
same string object. When s2 is assigned a new value, it points to a
new string object, while s1 remains unchanged. This demonstrates
the immutable nature of strings in C#.
$ dotnet run Original: s1 = hello, s2 = hello After change: s1 = hello, s2 = world
Because of immutability, strings are safe to share between variables and methods
without risk of accidental modification. However, frequent string modifications
can lead to performance issues due to the creation of many temporary string
objects. For scenarios requiring many changes, consider using
StringBuilder.
Copy by Reference Example
Reference types in C# store memory addresses rather than actual values. When a reference type variable is assigned to another variable, the memory address is copied, meaning both variables point to the same data. This behavior allows modifications to one variable to be reflected in the original object.
Unlike value types, which create separate copies when assigned, reference types maintain shared data across multiple variables. This is especially important when working with complex objects, such as arrays and class instances, where multiple references can interact with the same underlying memory.
int[] arr1 = { 1, 2, 3 };
int[] arr2 = arr1; // Copy by reference
Console.WriteLine($"Original: arr1[0] = {arr1[0]}, arr2[0] = {arr2[0]}");
arr2[0] = 100; // Affects arr1
Console.WriteLine($"After change: arr1[0] = {arr1[0]}, arr2[0] = {arr2[0]}");
In the example above, both arr1 and arr2 point to the
same array. When modifying arr2[0], the change is also applied to
arr1. Since arrays are reference types, their contents are not
duplicated during assignment.
Similarly, objects instantiated from a class behave in the same manner. When one object reference is assigned to another variable, both variables share the same memory address. Modifying one will affect the other.
Person person1 = new("Alice");
Person person2 = person1; // Copy by reference
person2.Name = "Bob"; // Affects person1
Console.WriteLine($"person1.Name = {person1.Name}, person2.Name = {person2.Name}");
class Person(string name)
{
public string Name { get; set; } = name;
}
The example demonstrates how class instances behave as reference types.
Initially, person1 contains the name "Alice." When assigned to
person2, both variables reference the same object. Changing
person2.Name updates person1.Name because they share
the same memory.
This characteristic of reference types enables efficient memory usage but also requires careful handling. Unintentional modifications to shared objects can introduce unintended side effects, making it essential to understand how variables reference data.
$ dotnet run Original: arr1[0] = 1, arr2[0] = 1 After change: arr1[0] = 100, arr2[0] = 100 person1.Name = Bob, person2.Name = Bob
To prevent accidental modifications, developers often use techniques such as cloning objects or using immutable types to ensure data integrity. Understanding how reference types function is fundamental to writing efficient and predictable C# programs.
Parameter Passing: Value, Reference, ref, and out
C# handles parameter passing in different ways, depending on whether a variable
is a value type or a reference type. By default, value type parameters are
copied, meaning modifications inside a method do not affect the original
variable. Reference type parameters, on the other hand, pass the reference,
allowing changes inside the method to be reflected outside it. Additionally, C#
provides the ref and out keywords to explicitly pass
variables by reference, enabling methods to modify the caller's variable.
Default Behavior: Value vs. Reference Types
When passing a value type parameter, a copy of the value is sent to the method, meaning modifications do not affect the original variable.
int number = 10;
ModifyValue(number);
Console.WriteLine($"After ModifyValue: {number}"); // Output: 10
void ModifyValue(int x)
{
x = 20; // Changes the local copy, not the original
}
In this example, the ModifyValue method does not affect the
original variable number. The value of number remains
10 after the method call. This is because number is a
value type, and its value is copied when passed to the method.
Reference type parameters pass a reference to the original object, meaning modifications inside the method will impact the original data.
int[] numbers = { 1, 2, 3 };
ModifyReference(numbers);
Console.WriteLine($"After ModifyReference: {string.Join(", ", numbers)}");
void ModifyReference(int[] arr)
{
arr[0] = 100; // Changes the actual array
}
In this example, the ModifyReference method modifies the first
element of the array numbers. The change is reflected outside the
method because the array is a reference type. The method receives a reference to
the original array, allowing it to modify the data directly. As a result, the
output shows that the first element of the array has been changed to
100.
Explicitly Passing by Reference Using ref
The ref keyword allows a method to modify a variable's value
directly in the caller's scope. Unlike standard value passing, changes inside
the method persist outside it.
int y = 5;
SetToTen(ref y);
Console.WriteLine($"y = {y}"); // Output: y = 10
void SetToTen(ref int n)
{
n = 10; // Modifies the original variable
}
The ref keyword must be used in both the method signature and the
method call. This indicates that the variable is passed by reference, allowing
the method to modify its value directly. This is particularly useful when you
need to return multiple values or when you want to avoid copying large data
structures.
Using out for Mandatory Output
The out keyword works similarly to ref, but it
indicates that the parameter must be assigned inside the method before exiting.
It is primarily used for returning multiple values.
int z;
Initialize(out z);
Console.WriteLine($"z = {z}"); // Output: z = 42
void Initialize(out int n)
{
n = 42; // Must be assigned before the method returns
}
The out keyword allows the method to return multiple values by
assigning values to the output parameters. Unlike ref, the
variable does not need to be initialized before being passed to the method.
The method is responsible for assigning a value to the out
parameter before returning. This is useful for methods that need to return
multiple values or when the output value is not known until the method
executes.
Key Differences Between ref and out
The ref keyword requires that the variable be initialized before it
is passed into the method, as it maintains its original value while allowing
modifications. The out keyword, however, does not require
initialization beforehand—the method is responsible for assigning a value before
returning.
Using these techniques strategically can optimize memory usage and enhance functionality when working with complex data structures or scenarios requiring multiple return values.
Summary and Best Practices
- Value types are copied by value; changes to one variable do not affect the other.
- Reference types are copied by reference; changes to one variable affect all references.
- Arrays are always reference types, even if their elements are value types.
- Use
refandoutto allow methods to modify variables in the caller. - To avoid unintended side effects, create deep copies of reference types when needed.
- Understand the difference to prevent bugs and write efficient, predictable code.
This tutorial covered copy by value and copy by reference in C#, including memory allocation, assignment, parameter passing, and best practices.
Source
C# reference types documentation
Author
List all C# tutorials.