When programming in C#, one of the foundational concepts that every developer should grasp is how data gets passed around – specifically, understanding the difference between passing by reference and passing by value. This distinction determines how data is transferred between methods, how memory is allocated, and most importantly, whether or not changes in one method will affect data elsewhere in your program.
Let’s see some code to better explain this:
internal class Program
{
static void Main(string[] args)
{
int number = 1;
int? nullable = null;
DateTime date = new DateTime(1111, 1, 1);
string text = "1";
List<int> list = new List<int> { 1, 1 };
Person person = new Person { Name = "1", Age = 1 };
byte[] bytes = new byte[4];
PersonStruct personStruct = new PersonStruct { Name = "1", Age = 1 };
ByValueOrByReference.ByValue(number, nullable, date, text, list, person, bytes, personStruct);
ByValueOrByReference.ByReference(ref number, ref nullable, ref date, ref text, ref list, ref person, ref bytes, ref personStruct);
}
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
public struct PersonStruct
{
public string Name { get; set; }
public int Age { get; set; }
}
public class ByValueOrByReference
{
public static void ByValue(int number, int? nullable, DateTime date,
string text, List<int> list, Person person,
byte[] bytes, PersonStruct personStruct)
{
// These will NOT change the value of the variables passed in
number = 2;
nullable = 2;
date = new DateTime(2222, 2, 2);
text = "2";
personStruct.Age = 2;
personStruct.Name = "2";
// This will NOT change the value of the variable passed in since we are assigning a new object
list = new List<int>();
list.Add(2); // This would apply in the original list if the line above did not exist
//These will change the value of the variable passed in
person.Age = 2;
person.Name = "2";
bytes[0] = 2;
}
public static void ByReference(ref int number, ref int? nullable,
ref DateTime date, ref string text, ref List<int> list,
ref Person person, ref byte[] bytes,
ref PersonStruct personStruct)
{
//All of these will change the value of the variable passed in
number = 2;
nullable = 2;
date = new DateTime(2222, 2, 2);
text = "2";
list = new List<int>();
list.Add(2);
person.Age = 2;
person.Name = "2";
bytes[0] = 2;
personStruct.Age = 2;
personStruct.Name = "2";
}
}
We have a class and a struct for a Person. We have created 2 methods, one called ByValue and one ByReference. As we can see the ByReference method will change all the parameters passed. The ByValue method will only change the properties of the class Person and the first element of the array, but why? When passing structs to a method, a defensive copy will be made in order to prevent changes outside of the method. When passing a class (the class String is an exception to this rule)* we are essentially passing a copy of the pointer that points to that class, that is what allows us to change the values of the properties of the Person class but cannot re-initialize it.
So keep in mind that unless you use the ref keyword, you ALWAYS pass a parameter by value.
Using the keyword ref allows us to pass the actual reference, hence all the members are changed even outside the scope of the method.
While the ref keyword allows us to modify the actual reference, it’s essential to use it judiciously and be aware of its implications on our code’s clarity and maintainability.
In a future blog post we will learn more about defensive copies and how to make your code more performant with less memory allocations.
*Strings, although being reference types, are immutable. This means any modifications to a string will result in a new string object being created. Hence, even if you modify a string inside a method, it doesn’t change the original string variable outside.
Leave a comment