Pointer and Reference in C#
These rules will allow you to find the balance between complexity and function to effectively utilize pointers in C#.
Jan 9, 2020 • 6 Minute Read
Introduction
To begin, let's clarify some basic terms. In C# we have two types of codes, safe and unsafe. We call a code safe when that code runs entirely under the supervision of the CLR (Common Language Runtime). Unsafe codes are codes that use external resources such as files, music or video files, or network streams. With safe code the GC (Garbage Collector) releases resources that are not needed anymore. With unsafe code, we need to either call GC.Collect() or implement mechanisms to release the unused resources.
Compiling Apps with Pointers
A pointer is simply a variable that holds the memory address of another type or variable. By default, C# does not allow you to use pointers in your apps. The examples in this guide are tested for Visual Studio 2017 or above.
Let's take this example.
using System;
namespace Pointers
{
class refderef
{
static void Main(string[] args)
{
int* myPointer;
Console.ReadKey(); ;
}
}
}
You will see the following error when you declare a pointer.
Note: Pointers and fixed size buffers may only be used in an unsafe context.
Alright, what is the unsafe context in C#?
It looks something like this.
unsafe {
int* myPointer;
}
But this wrapper alone is not enough to solve the problem. We need to enable the following.
Using Pointers
When you are working with an unsafe context, you may have three different types:
- Pointer
- Value
- Reference
Let's assume we are using the unsafe wrapper around our declarations so we can reduce the code size and focus more on pointers.
A skeleton for the declaration of a pointer looks like this:
<type>* <name>;
An actual example of it would be:
int* myPointer;
Alone, a pointer is not really good for much. It is just a special variable that points to the memory location of another variable. The type we specify before the * is called a referent type. Only unmanaged code can be a referent type, which is very important.
We can declare multiple pointers on the same line.
string* firstName, lastName, nickName;
Note: A pointer cannot point to a reference or to a struct that contains references.
You could also define a pointer to a pointer or double pointer.
double** myDouble;
This means a pointer to a pointer to a double. These are typically used when you would like to swap out or modify the value of a pointer based on another pointer.
Let's check how the value and addresses look in an example.
using System;
namespace Pointers
{
class refderef
{
static void Main(string[] args)
{
unsafe {
int myNumber = 10;
int* myPointer;
int** myPointerPointer;
myPointer = &myNumber;
myPointerPointer = &myPointer;
Console.WriteLine($"The value of {nameof(myNumber)} is {myNumber}, the address of {nameof(myPointer)} is {(int)myPointer}, and address of {nameof(myPointerPointer)} is {(int)myPointerPointer}!");
}
Console.ReadKey(); ;
}
}
}
This produces the following output:
The value of myNumber is 10, the address of myPointer is 9432328, and address of myPointerPointer is 9432324!
If you prefer,you can use the unsafe keyword in method declartions, too. When you do that, you do not have to wrap your pointer code or anything related to it, with the unsafe statement, as the function definition will provide this wrapper.
Let's demonstrate this with the following code:
using System;
namespace Pointers
{
class refderef
{
static unsafe void unsafeMethod() {
int myNumber = 10;
int* myPointer;
myPointer = &myNumber;
Console.WriteLine($"Coming from unsafe method: {nameof(myNumber)} value is {myNumber}, and the pointer {nameof(myPointer)} with address: {(int)myPointer} has the following value: {*myPointer}");
}
static void Main(string[] args)
{
unsafeMethod();
Console.ReadKey(); ;
}
}
}
This gives us the following output when called.
Coming from unsafe method: myNumber value is 10, and the pointer myPointer with address: 15724316 has the following value: 10
The GC process will do behind the scenes memory management and move objects around in memory. We have the option to fix an object in memory with the fixed keyword.
This keyword is used typically to create tables in which we describe to the GC which objects are to remain fixed in specific regions of the executable code. This can result in fragmentation of the heap, so it needs to be used with care. Fix objects only when it's absolutely necessary.
Reference and Dereference
We have demonstrated how to work with pointers in the above code. Now comes the question, "What are the & and \ operators?" The operator & is called a reference operator, and it serves as a way to retrieve the address of a variable. This in turn can be assigned to a pointer. The \ operator is called dereference operator. It is used to get the value from the address referenced by the pointer.
Conclusion
As we learned from this guide, pointers are associated with unsafe contexts, which may make them seem harder to wield and utilize in your applications. On one hand, this is true because they introduce additional complexit, and more things can go wrong. However, the rules we laid out allow you to find the balance and utilize this feature of C# to your own advantage. Don't forget to memorize the difference between the unsafe keyword in function context and the alternative. I hope this was worth your time, and you found what you were looking for.