I want to start this post by defining what a reference type is and what a value type is. We’ll start with reference types. These are all objects that are not value types, pretty easy, huh? 🙂 So what are value types? Per the documentation they are numeric types (like int, decimal, float and other numeric primitives), bool, enums and structs. The big thing to consider between these two types is that when calling a method, the reference type is passed as a pointer to the original object and a value type is passed as a copy of the original value. There are more differences but they are outside of the scope of this post.
In C# when you’re passing an object into a method with a reference type you’re not getting a copy of the object but a copy of the pointer to where the object is stored in memory. This allows you to change any parameters or values on the object and they will persist when you return from the method call. There is a problem, however, when you create a new object and overwrite the existing object. The code that called into the method doesn’t know about that change. The change only applies within the scope of the method. I mean, all you’ve passed in is the pointer and you’ve now over-written that pointer locally. The original calling code still has it’s original pointer but within your method you’ve now replaced that pointer with something else.
Imagine the following code:
class Program
{
static void Main(string[] args)
{
Person testPerson1 = new Person("original name");
Debug.WriteLine("Person's name = " + testPerson1.Name);
NameChanger.CreateNewPerson(testPerson1);
Debug.WriteLine("Person's name = " + testPerson1.Name);
}
}
public class Person
{
public Person(string name)
{
this.Name = name;
}
public string Name { get; set; }
}
public class NameChanger
{
public static void CreateNewPerson(Person testObject)
{
testObject = new Person("new name");
}
}
This results in the output:
Person's name = original name
Person's name = original name
Well, what if you want to create a new object and have it stay around after leaving the method? That is where the “ref” keyword comes into play (“out” is also relevant but I’ll discuss that later). The definition of the “ref” keyword per the documentation is:
The ref keyword causes an argument to be passed by reference, not by value. The effect of passing by reference is that any change to the parameter in the method is reflected in the underlying argument variable in the calling method. The value of a reference parameter is always the same as the value of the underlying argument variable.
At first, when reading it, it’s easy to think that this is just redundant. I mean, come on, we’re already passing the object by reference. Well, that first part is really important for value-types and it is rather redundant when it comes to reference types. What’s important is that second sentence. The underlying argument variable in the calling method is changed. You are actually changing the pointer in the original calling code.
Let’s change our code to use the ref keyword instead:
class Program
{
static void Main(string[] args)
{
Person testPerson1 = new Person("original name");
Debug.WriteLine("Person's name = " + testPerson1.Name);
NameChanger.CreateNewPersonByRef(ref testPerson1);
Debug.WriteLine("Person's name = " + testPerson1.Name);
}
}
public class Person
{
public Person(string name)
{
this.Name = name;
}
public string Name { get; set; }
}
public class NameChanger
{
public static void CreateNewPerson(Person testObject)
{
testObject = new Person("new name");
}
public static void CreateNewPersonByRef(ref Person testObject)
{
testObject = new Person("new name by ref");
}
}
This results in the output:
Person's name = original name
Person's name = new name by ref
So how does “out” come into play in all this? Well, the primary differences between “out” and “ref” is that with ref the object has to exist before it is passed in and with out it has to be created before the method that was called exits. Out can be called like ref, where you instantiate the object before calling, except you are guaranteed that the object will be overwritten. With ref the overwrite could be conditional and is not guaranteed to happen.
Here is the code using the out keyword:
class Program
{
static void Main(string[] args)
{
Person testPerson1;
NameChanger.CreateNewPersonByOut(out testPerson1);
Debug.WriteLine("Person's name = " + testPerson1.Name);
}
}
public class Person
{
public Person(string name)
{
this.Name = name;
}
public string Name { get; set; }
}
public class NameChanger
{
public static void CreateNewPerson(Person testObject)
{
testObject = new Person("new name");
}
public static void CreateNewPersonByRef(ref Person testObject)
{
testObject = new Person("new name by ref");
}
public static void CreateNewPersonByOut(out Person testObject)
{
testObject = new Person("new name by out");
}
}
This results in the output:
Person's name = new name by out
Okay, so all we have left is pass by value, which only happens on value types. When calling a method with a value type a copy of that variable is sent into the method. But what if we want to replace that value in the calling method? Well, out and ref work with value types as well as reference types. How is this possible you ask? Hmm, it’s good to remember that ultimately everything in C# can be represented by an object.
We may thing of primitives like ints, doubles, enums and structs as just values and that’s because they are. In fact they’re actually stored in a different section of memory then reference types. But here is an interesting thing. A struct is actually of the class “ValueType”, which by it’s very nature of being a class extends Object. Unless it’s specifically converted to an object, however, it’s just treated like a value. When out and ref are used the CLR does what is called “boxing” and wraps the value in the appropriate object so it can be treated as a reference type by the system on the back end. Then, when it’s no longer needed as an object it’s “unboxed” back to being a value type. For a better write up on boxing and unboxing see the MSDN reference.
Here is the code from above but using a “PersonHolder” wrapper around a person:
class Program
{
static void Main(string[] args)
{
PersonHolder person1 = new PersonHolder { person = new Person("struct Person 1") };
Debug.WriteLine("Person's name = " + person1.person.Name);
//still a value type
NameChanger.CreateNewPerson(person1);
Debug.WriteLine("Person's name = " + person1.person.Name + Environment.NewLine);
PersonHolder person2 = new PersonHolder { person = new Person("struct Person 2") };
Debug.WriteLine("Person's name = " + person2.person.Name);
//box it into a "ValueType" object
NameChanger.CreateNewPersonByRef(ref person2); //upon returning unbox it back into a struct
Debug.WriteLine("Person's name = " + person2.person.Name + Environment.NewLine);
PersonHolder person3;
//nothing to box since person3 doesn't exist yet
NameChanger.CreateNewPersonByOut(out person3); //take the pointer that set in the method and unbox it back to a struct
Debug.WriteLine("Person's name = " + person3.person.Name);
}
}
public struct PersonHolder
{
public Person person;
}
public class Person
{
public Person(string name)
{
this.Name = name;
}
public string Name { get; set; }
}
public class NameChanger
{
public static void CreateNewPerson(PersonHolder testObject)
{
testObject = new PersonHolder { person = new Person("struct Person - create new") };
}
public static void CreateNewPersonByRef(ref PersonHolder testObject)
{
testObject = new PersonHolder { person = new Person("struct Person - new name by ref") };
}
public static void CreateNewPersonByOut(out PersonHolder testObject)
{
testObject = new PersonHolder { person = new Person("struct Person - new name by out") };
}
}
This results in the output:
Person's name = struct Person 1
Person's name = struct Person 1
Person's name = struct Person 2
Person's name = struct Person - new name by ref
Person's name = struct Person - new name by out
That’s it for now.
Thanks,
Brian