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
ref
andout
to 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.