You are confusing two concepts which are instead different concepts.
The concepts I'm referring to are the followings:
- the difference between value types and reference types
- the difference between passing arguments by value and passing arguments by reference
Let's start with value type VS reference type.
Value types are types whose value is the data itself. When I say "tha data itself", I mean an actual instance of the value type. An example of a value type is the struct named System.Int32, which is a 32 bit signed integer number. Consider the following variable declaration:
int number = 13;
In this case the number variable contains the actual value 13, which is an instance of the System.Int32 type. Put another way, the memory location in your computer memory you access via the number identifier directly contains the integer number 13.
Based on what I explained above, the following lines of code create a copy of the value contained inside the a variable and assign the copy to the b variable. After the execution of the code the two variables contain indipendent copies of the same integer value (13). Put another way, there are two indipendent memory locations in your RAM which contain two indipendent copies of the integer number 13:
int a = 13;
int b = a;
Reference types are types whose value is a reference to the data itself. When I say "a reference to the data itself", I mean a reference to an instance of the type. An example of a reference type is the class System.String, which is used to represent string of characters.
The lifetime of reference type instances is handled by the garbage collector, which is in charge of doing the deallocation of the memory occupied by instances of the reference types. This is done when these instances are no more referenced by anyone, so they can safely be removed from memory.
Consider the following line of code:
string name = "Enrico";
Here the variable name does not contain the string "Enrico", instead it contains a reference to the string "Enrico". This means that somewhere in the computer memory there is a memory address containing the actual data (the sequence of characters composing the string "Enrico") and that the memory location you access via the name identifier contains a reference to the memory location containing the actual string data. You can imagine the thing I'm calling a reference, as a fictious arrow (a pointer) which points to another memory location, which actually contains the sequence of characters composing the string "Enrico".
Consider the following code:
string a = "Hello";
string b = a;
This is what happens here:
- some memory is allocated to contain the sequence of characters composing the string "Hello". At this memory location there is the real data, the string itself, the actual instance of the System.Stringtype.
- the variable acontains a pointer which points to the real data, that is a pointer to the memory location described at step 1.
- the variable bcontains a copy of the content of variablea. This means that the variablebcontains a pointer pointing to the memory location described at step 1. There are now 2 indipendent pointers to the same memory location, which contains the actual data, that is the sequence of characters composing the string"Hello".
Notice that, at this point, you can access the same instance (the string "Hello", which is an instance of the System.String type), by using two different pointers: both the a and b variables are referencing the string data stored somewhere in computer memory. The very important part here is that there is only one string instance in memory.
We can now talk about pass by value and pass by reference. Simply put, by default in C# all the method arguments are passed by value. This is the most important part of my answer and I have noticed many times some confusion about this. But it's really that simple: unless you specify that you want a pass by reference behavior, the default behavior of C# is passing method arguments by value. You can opt out by this default behavior by using the ref or the out keywords: this way you decide that you do want a pass by reference behavior when you pass arguments to a method.
Passing by value means passing a copy of the value to the method as an argument.
What is really important to understand is what "a copy of the value" actually means. But you already know the answer:
- a copy of the value for a value type, means a copy of the actual data representing the instance of the type
- a copy of the value for a reference type, means a copy of a reference to the actual data. You are not creating a copy of the actual object stored in memory, you are creating a copy of a pointer to that object.
Now we can consider the final example, that I hope will clarify your doubt.
I need a class (which is a reference type) and a couple of methods.
public class Person 
{
  public string Name { get; set; }
}
public static void DoSomething(Person person) 
{
  person = new Person 
  {
    Name = "Bob"
  };
  Console.Writeline(person.Name); // this prints Bob
}
public static void Main(string[] args) 
{
  Person alice = new Person 
  {
    Name = "Alice"
  };
  DoSomething(alice);
  Console.Writeline(alice.Name); // this prints Alice
}
Here is what happens:
- the Mainmethod creates an instance of thePersonclass in the computer memory, whose name is"Alice". A variable namedaliceis assigned to that instance, so the variablealicecontains a pointer to thePersonclass instance. There is 1 variable and 1 object in memory. The variable points to the object.
- the DoSomethingmethod is invoked and the variablealiceis passed by value as the argument for thepersonparameter. The variablepersonis a copy of the variablealice: these two copies are independent and both of them point to the same memory location, which contains the object created at point 1 (thePersonclass instance whose name is"Alice").
- inside the method DoSomethinga new object is created in memory, this object is an instance of thePersonclass having the name"Bob". The method parameter,person, is assigned the newly created object. There are now two objects in memory, both of them are instances of thePersonclass. The parameterpersoncontains a reference to one of these objects (the one having name"Bob"), while the variablealiceof theMainmethod contains a reference to the other object (the one having name"Alice"). This is perfectly fine because there is no bound between the parameterpersonand the variablealice: they are totally independent and they are free to reference different objects.
- when the execution of DoSomethingends, the method parameterpersongoes out of scope and is no more accessible via code. We are back to theMainmethod and the variablealiceis still in scope and accessible via code. This variable has not been modified by the execution ofDoSomethingand keeps pointing to the instance of thePersonclass created at point 1 (the one having name"Alice").