Understanding List Parameter Passing in C#: Reference Types vs. ref Keyword

Dec 08, 2025 · Programming · 9 views · 7.8

Keywords: C# | parameter passing | reference types | ref keyword | List<T>

Abstract: This article provides an in-depth analysis of the behavior of List<T> as a reference type when passed as method parameters in C#. Through a detailed code example, it explains why calling the Sort() method affects the original list while reassigning the parameter variable does not. The article clearly distinguishes between "passing a reference" and "passing by reference using the ref keyword," with corrected code examples. It concludes with key concepts of reference type parameter passing to help developers avoid common misconceptions.

Introduction

In C# programming, understanding parameter passing mechanisms is crucial for writing correct and efficient code. This article examines a specific case to analyze the behavior of List<int> as a method parameter, particularly explaining why certain operations affect the original object while others do not. This case stems from a common misconception: many developers believe that when a reference type is passed as a parameter, any modification to that parameter within the method will be reflected in the original variable.

Problem Case and Analysis

Consider the following C# code example:

class Test
{
    List<int> myList = new List<int>();

    public void TestMethod()
    {
        myList.Add(100);
        myList.Add(50);
        myList.Add(10);

        ChangeList(myList);

        foreach (int i in myList)
        {
            Console.WriteLine(i);
        }
    }

    private void ChangeList(List<int> myList)
    {
        myList.Sort();

        List<int> myList2 = new List<int>();
        myList2.Add(3);
        myList2.Add(4);

        myList = myList2;
    }
}

The developer expected the output to be 3 and 4, but the actual output is 10, 50, 100. This raises two key questions: why does the Sort() operation take effect, while the myList = myList2 assignment does not?

Core Concepts: Reference Types and Parameter Passing

To understand this behavior, two important concepts in C# must be clarified:

  1. Reference Types: List<T> is a reference type, meaning the variable myList stores a reference (similar to a pointer) to a List object on the heap, not the object itself.
  2. Parameter Passing Mechanism: By default, C# uses pass-by-value. For reference types, a copy of the reference is passed, not the reference itself.

When TestMethod calls ChangeList(myList), the following process occurs:

// The myList variable in TestMethod contains reference R1 (pointing to the original List object)
// The ChangeList parameter myList receives a copy R1_copy of reference R1
// Both R1_copy and R1 point to the same List object

Analysis of Operation Effects

Now analyze the two operations in the ChangeList method:

  1. myList.Sort(): Accesses and modifies the List object on the heap through the reference copy R1_copy. Since R1 and R1_copy point to the same object, this modification is visible to TestMethod.
  2. myList = myList2: Changes the value of the parameter variable myList (i.e., R1_copy) to point to a new List object (myList2). However, the original variable myList in TestMethod (holding R1) still points to the original List object, so this assignment does not affect TestMethod.

This can be represented with a memory diagram: initially, both TestMethod.myList and ChangeList.myList point to the same List object. The Sort() operation modifies the content of this shared object. Then myList = myList2 causes ChangeList.myList to point to a new object, but TestMethod.myList still points to the original (now sorted) object.

Solution: Using the ref Keyword

If you want the assignment operation in ChangeList to also affect TestMethod, use the ref keyword:

private void ChangeList(ref List<int> myList)
{
    myList.Sort();

    List<int> myList2 = new List<int>();
    myList2.Add(3);
    myList2.Add(4);

    myList = myList2; // Now this modifies myList in TestMethod
}

The call must also be modified accordingly:

ChangeList(ref myList);

With ref, a reference to the variable myList is passed (i.e., a reference to a reference), so any assignment to the parameter in ChangeList directly affects the original variable in TestMethod.

Key Differences Summary

<table border="1"> <tr><th>Aspect</th><th>Passing Without ref</th><th>Passing With ref</th></tr> <tr><td>What is Passed</td><td>Copy of the reference</td><td>Reference to the reference</td></tr> <tr><td>Modifying Object Content</td><td>Affects original object</td><td>Affects original object</td></tr> <tr><td>Reassigning Parameter</td><td>Does not affect original variable</td><td>Affects original variable</td></tr> <tr><td>Typical Use Case</td><td>When only object state needs modification</td><td>When the entire object needs replacement</td></tr>

Practical Recommendations

In practical development:

  1. In most cases, passing without ref is sufficient, as we usually only need to modify the object's content, not replace the entire object.
  2. Use ref with caution, as it increases code coupling and can make understanding code flow more difficult.
  3. Consider using return values instead of ref parameters to return new objects, as this is often clearer.
  4. For collection types, note that methods like Sort() modify the collection in place, while methods like OrderBy() return new collections.

Conclusion

The parameter passing mechanism for reference types in C# is key to understanding many programming issues. When List<T> is passed as a reference type without ref, modifications to the object's content within a method affect the original object, but reassigning the parameter variable does not affect the original variable. This distinction arises because a copy of the reference is passed, not the reference itself. When complete replacement of the object referenced by the original variable is needed, the ref keyword should be used. Understanding this mechanism helps avoid common errors and write more reliable C# code.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.