A parte, no podemos declarar los punteros de cualquier forma, tenemos que utilizar la palabra reservada unsafe, que se puede aplicar a constructores, métodos, propiedades y bloques de código, ahora podemos hacer uso del denominado código inseguro, pero realmente no es que sea código inseguro. Esto es para indicar que ese código se ejecutará en un contexto no gestionado, es decir, que el CLR no controlará la asignación y liberación de la memoria.
Los punteros pueden ser de tipo valor (int, long, char…), arrays, cadenas o estructuras que no contengan miembros de tipo referencia. El uso de punteros y su aritmética, es prácticamente igual que en C/C++, un ejemplo:
unsafe
{
int* p1, p2; // p1 y p2 son de tipo int*
int *p3, *p4; // no es correcto
int x = 10;
p1 = &x;
p2 = p1; // p1 y p2 apuntan al objeto almacenado en x
Console.WriteLine( p1 == p2); // imprime por pantalla True
}
Es algo trivial pero podemos observar como podemos declarar punteros y asignar variables.
El tipo de puntero, en este caso int, indica cuál es el tipo de variable que se ha de considerar que se almacenan en la dirección de memoria almacenada por el puntero, esto permite perfectamente que un puntero de tipo int apunte realmente a un char, pero lo veremos como si fuese un int. A parte tenemos un tipo especial de puntero, el puntero void* con el que no definimos un tipo concreto para la varaible a la que apuntamos.
Podríamos tener punteros apuntado a variables almacenadas en memoria dinámica y esto podría ser un problema si el recolector de basura se pusiera en funcionamiento y cambiara alguna de estas variables de lugar, como resultado tendríamos punteros no válidos, para evitar esto, C# introduce una nueva palabra reservada: fixed, que sirve para que una variable no sea movida del lugar donde se encuentra en memoria mientras un puntero apunte a ella. Aquí tenemos un ejemplo de cómo podemos alterar el contenido de una cadena (String) mediante el uso de punteros:
class UnsafeString
{
public static void Main(string[] args)
{
String s = "Una cadena de ejemplo";
Console.WriteLine("Cadena inicial: " + s);
unsafe
{
fixed (char* ps = s)
{
for (int i = 0; i < s.Length; i++)
if (ps[i ] == ' ') // sustituimos los espacios en blanco
ps[i ] = '_'; // por guiones bajos
}
}
Console.WriteLine("Cadena final: " + s);
}
}
La aritmética para punteros nos permite aumentar o disminuir la dirección a la que apunta el puntero, el operador ++ (--) no aumenta (disminuye) en uno la dirección a la que apunta el puntero, sino que le suma (resta) el tamaño del tipo al que apunta. Si utilizamos el operador + (-) aumentamos (disminuimos) el lugar al que apunta el puntero tantas veces como se indique en el valor que sumamos (restamos). Esto nos puede resultar muy útil para recorrer matrices de una forma más eficiente, pues no se comprueba el valor de los índices, eso queda para el programador. También disponemos del operador [ ] que nos permite acceder directamente a un elemento determinado de la matriz, veamos un ejemplo:
int [,] matrix = new int[10, 10];
for (int i = 0; i < 10; i++)
for (int j = 0; j<10; j++)
matrix [i,j] = j + i*10;
unsafe
{
fixed (int* pMatrix = matrix)
{
for (int i = 0; i < matrix.Length; i++)
Console.Write(*(pMatrix + i) + ", ");
//Console.Write(pMatrix[i ] + ", "); equivalente a la linea anterior
}
}
Con estos ejemplos tenemos una primera toma de contacto con el uso de los punteros, pero tal vez podríamos complicarlo tanto como quisiéramos y darnos cuenta de que pueden ser tan “horrendos” como en C/C++.