DEV Community

Mahmoud Mahmoud
Mahmoud Mahmoud

Posted on • Edited on

C# Passing by Value vs Passing by Reference

Image description

Have you ever wondered why changes inside a function sometimes affect your variables, and other times do not? offers two ways to pass data: by value (copy) or reference (pointer). This article breaks down the distinction between the two, and how each works. Let's learn to control our data flow and write rock-solid code!

Understanding Value Types and Reference Types

The two main categories Of types are reference and value A value type variable holds an instance Of the type. A reference variable, on the other hand, contains a reference to an instance of the type. Value types are allocated on the stack and deallocated when the stack unwinds or their containing type gets deallocated. This contrasts reference types, which are allocated on the heap and garbage-collected. Value-type variables can be used as fields in reference-type objects. To dive into how .NET treats value and reference types check out Choosing Between Class and Struct

Value types are the built-in types in C# like int, long, double, etc., or user-defined types like struct types. on the other hand, reference types are like arrays, classes, and so on.

Let's define some value types variables:

int discountRate = 50;
double productAPrice = 60;
Enter fullscreen mode Exit fullscreen mode

Now let's define some reference type variables:

First, let's define our Product class to hold our ProductId, and Price.

public class Product
{
    public int ProductId { get; set; }
    public int Price { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Then let's use our class to create a new object from it like:

var product = new Product
{
    ProductId = 1,
    Price = 60
};
Enter fullscreen mode Exit fullscreen mode

Passing by Value

By default, when we pass a parameter into our method we pass it by value, which means it copies the object value to our method. Therefore, any modifications in our method do not affect the original variable. This applies only to the built-in or struct types.

Let's update the price from our UpdatePriceByPercent method:

UpdatePriceByPercent(productAPrice, discountRate);

public static void UpdatePriceByPercent(double price, int discountRate)
{
    price = price - (price β€’ discountRate) / 100;
}
Enter fullscreen mode Exit fullscreen mode

After calling the method, nothing changed; the value productAPrice is still the same. Every parameter passed to this method has a copy of the original variable value.

So, we use another approach by defining the CalculateDiscount method:

public double productANewPrice = CalculateDiscount(ProductAPrice, discountRate);

public static double CalculateDiscount(double price, int discountRate) {
    return price - (price * discountRate) / 100;
}
Enter fullscreen mode Exit fullscreen mode

In this approach, we defined a method that returns the new price after applying the discount, assigning this value to a new variable to use later in our program.

Here we passed the price and discount rate as individual parameters and got a new value to deal with. But what if we need to modify the original variable itself? To handle this scenario, we pass the price by reference.

Passing by Reference

In general, passing by reference means you pass a reference that refers to an object. Therefore, we use the variable directly without creating any new copy. This technique is useful in many scenarios. This approach is straightforward in C#. The ref keyword allows us to access the data and make changes that persist.

ref Keyword

Let's clear things out/ All objects we create from classes are reference-type variables that passed by reference by default, and all objects we create from a struct are value-type variables that passed by value by default.

Going back to our code, let's update the price within our UpdatePriceByPercentByRef method:

UpdatePriceByPercentByRef(ref productAPrice, discountRate);

public static void UpdatePriceByPercentByRef(ref double price, int discountRate) {
    price = price - (price * discountRate) / 100;
}
Enter fullscreen mode Exit fullscreen mode

We defined UpdatePriceByPercentByRef method with two parameters. The first double prefixed with the ref keyword allows us to use it by reference. On the other hand, the second parameter, an int, is passed by value. When we call the method, we pass the productAPrice variable prefixed with the ref keyword (this is required or throws a compile error). After calling this method, the value of productAPrice is updated.

out Keyword

Here, we encounter an error if productAPrice is not initialized before passing it as a ref parameter. This is true because we are trying to use a reference to an uninitialized variable. But, we can use the out keyword to use the variable with no need to initialize it. Let's see how to do that:

double price;
MakeItZero(out price);

public static void MakeItZero(out double price) {
    price = 0;
}
Enter fullscreen mode Exit fullscreen mode

Here, we assigned the unassigned variable price to zero.

Arrays

Now, let's use an array of double prices to increase them by 10 percent each:

double[] prices = { 30, 15, 21.5, 50 };

UpdatePrices(prices, 10);

public static void UpdatePrices(double[] prices, int rate) {
    for (int i = 0; i < prices.Length; i++)
    {
        prices[i] = prices[i] + prices[i] * rate / 100;
    }
}
Enter fullscreen mode Exit fullscreen mode

We defined UpdatePrices method that takes an array of double and an int. It updates the values in the array by increasing or decreasing them based on the given rate. After this, we define prices as double[] and initialize with values. Then, we call the UpdatePrices method and pass the prices array to it. Note that the ref keyword is not required. Finally, all the values in the array increased by 10%.

Classes and Objects

Let's create a method that takes a product and an int discount rate to update its price:

public static void ApplyDiscount(Product product, int discountRate)
{
    product.Price -= (product.Price * discountRate) / 100;
}
Enter fullscreen mode Exit fullscreen mode

Here, there is no need to use the ref keyword and the price is updated after calling the method.

ref readonly modifier

Then we have ref readonly which allows referencing a variable without permitting its value to be changed. The question arises: why use it?

We use it to indicate that a large struct should be passed by reference for reasons.

The ref readonly modifier introduced in C# 12, offers a balance between and safety.

Key Differences and Considerations

Passing by value is the default behavior for value-type and struct-type variables, and passing by reference is the default behavior for class-type variables or arrays.

We pass by reference when we need to modify the actual value or state, or when we want to gain more performance dealing with large struct-type variables. Being able to use the ref readonly modifier also avoids unnecessary copying and accesses the heap memory address more safely without allowing modifications.

Conclusion

Passing by value means any modifications in the method body do not affect the original variable value.

Passing by reference means passing the original data location in memory. That means any modifications within the method affect the original variable value.

Built-in types and struct types in C# are passed by value by default. Passing by reference is the default behavior for arrays and classes.

When passing an array or an object to a method, the method gets a reference to the entire object in the memory

We use the ref keyword to pass variables by reference, and the out keyword to define a method that returns values by reference.

Consider data size and the need for modification. We use passing by value for small data and passing by reference for large objects to improve performance and control the changes.

Finally, passing by reference can introduce complexity. Use it judiciously, and always with proper data handling practices.

Top comments (1)

Collapse
 
hudash profile image
Huda Shakir

Thank you for the great explaination πŸ”₯