2009-09-14 10 views
11

En C++, que puede hacer algo como esto:¿Referencias a variables en C#?

int i[10] = new int[10]; 
int *p = &i[5]; 

Entonces, siempre se puede saber que los puntos de P al 5º elemento de la matriz int i, independientemente de contenidos i.

¿Hay alguna manera de hacer algo similar en C#?

Me doy cuenta de que esta es probablemente una de las formas en que C# "nos protege" de nosotros mismos, así que no busco un equivalente exacto, sino un concepto similar ... es decir, poder consultar el contenido de alguna otra variable, en lugar de la instancia de la variable en sí.

Aquí está mi caso de uso en el que estoy pensando. Tengo una serie de cadenas. Me gustaría tener otra serie de referencias a esos elementos de la matriz. Algo como esto (obviamente no código válido):

string[] s = new string[] { "one", "two", "three", "four", "five", "six" }; 
stringref[] sr = new stringref[] { &s[0], &s[1], &s[2], &s[3], &s[4], &s[5] }; 

Console.WriteLine(sr[1]); // == "two" 
s[1] = "two point zero"; 
Console.WriteLine(sr[1]); // == "two point zero" 

Ciertamente, paramets árbitro hace esto, y fuera parámetros le permiten escribir en una variable específica. ¿Pero qué hay con los no-parámetros? ¿Puedes guardar un ref? ¿Puedes mantener una variedad de refs o un diccionario?

Parece que si la capacidad de hacerlo con parámetros está presente, debería haber una forma de hacerlo sin ellos.

+0

Su pregunta parece confundir las referencias con punteros, y utiliza algunos términos bastante confusos como "los contenidos" de alguna variable y "variable en sí misma". 'int * p = & i [5]' asigna la * dirección * del quinto elemento de la matriz 'i' a la variable' p', que se declara como un puntero a 'int'. Las referencias no son lo mismo que los punteros. No estoy seguro acerca de C#, pero desde una perspectiva de C++ no está claro lo que estás preguntando. –

+0

@JamieBullock - Creo que es bastante claro, ya que todos los demás entendieron de lo que estaba hablando. Las referencias en C++ son más o menos lo mismo que los punteros, excepto que no se pueden hacer cálculos matemáticos o reasignarlos. De todos modos, las referencias C# no son las mismas que las referencias C++, y C# no tiene un concepto de punteros (cuando no se usa código inseguro), por lo que las referencias son las que usarías en C# ya que no hay nada más. Además, dije "instancia de la variable en sí". –

Respuesta

18

Poniendo el código unsafe a un lado, lo que permite mantener punteros a ubicaciones de memoria, no hay forma de almacenar una referencia a una variable en C#.

ref y out argumentos proporcionan el único medio para tomar una referencia pero no puede guardarlos en ningún lado.

Puede evitar esta limitación al envolver los campos en un class y utilizar su referencia en su lugar. Esto es lo que hace el compilador para capturar las variables en los cierres:

Por ejemplo, cuando escribe:

int integer = 0; 
Action<int> method = i => Console.WriteLine(i + integer); 
integer = 42; 
method(100); // prints 142, not 100 

En la segunda línea, el compilador tendrá que sacar el método anónimo y almacenarlo como una método separado en la clase. Obviamente, ese método no tendrá acceso a la variable integer. De alguna manera necesita pasar una "referencia" a la variable integer a ese método anónimo. Como no es posible, generará un class con un campo para contener un número entero y usa una instancia de esa clase para almacenar la variable. Básicamente, la variable local se promueve a un campo en una clase y se almacena en el montón.

+0

Eso es bastante interesante y me da algunas ideas. Gracias. –

-1

Sospecho que está haciendo la pregunta equivocada ...

Considere el siguiente código C#:

MyClass[] arr = new MyClass[] { MyClass(1), MyClass(2) .... }; 
MyClass obj = arr[5]; 

En este caso, es obj se refieren al mismo objeto como matriz [5 ], ya que tanto arr [5] como obj son referencias.

El problema con el código de muestra que proporcionaste fue que cuando escribes "s [1] =" dos punto cero "", en realidad no estás cambiando la cadena en la memoria; estás haciendo la referencia en la matriz apunta a una cadena diferente.Básicamente, su código C# es equivalente a la siguiente en C:

char **s = malloc(...); 
... set s's members 
char *sr = malloc(...); 
sr[1] = s1; 
s[1] = "two point zero"; 
// now, s[1] is "two point zero" while sr[1] is still "one" 
+0

No, estoy haciendo la pregunta correcta. Tu punto es exactamente lo que estoy tratando de evitar. Una referencia de objeto apunta a una instancia de los datos, no a la ubicación de los datos. –

+0

Pero esa ES la ubicación de los datos. La matriz de cadenas es solo la ubicación de la referencia ... –

+0

No, todavía no lo entiendes. Sugiero que vuelvas a leer mi pregunta y algunas de las respuestas que se han dado. Me estás diciendo algo que ya sé. Sé que obj se refiere al mismo objeto. Estoy buscando una forma de * NO * referirme a la instancia real del objeto, pero a la referencia misma, de la misma manera que funcionan los parámetros de ref. –

6

Una referencia a un array de sólo lectura:

class ArrayRef<T> 
{ 
    private T[] array; 
    private int index; 

    public ArrayRef(T[] array, int index) 
    { 
     this.array = array; 
     this.index = index; 
    } 

    public static implicit operator T(ArrayRef self) 
    { 
     return self.array[self.index]; 
    } 
} 

var s = new string[] { "one", "two", "three", "four", "five", "six" }; 
var sr = new ArrayRef<string>[] { new ArrayRef<string>(s, 0), new ArrayRef<string>(s, 1), new ArrayRef<string>(s, 2), new ArrayRef<string>(s, 3), new ArrayRef<string>(s, 4), new ArrayRef<string>(s, 5) }; 

Console.WriteLine(sr[1]); // == "two" 
s[1] = "two point zero"; 
Console.WriteLine(sr[1]); // == "two point zero" 
+0

Había pensado en algo parecido a esto, pero nunca se me ocurrió nada. Esto me da algunas ideas, junto con la respuesta de Mehrdad. –

3

En las referencias de código administrado se utilizan en lugar de punteros, como el recolector de basura se puede mover objetos en la memoria en cualquier momento.

Para tener una referencia a algo tiene que ser un objeto, por lo que no puede tener referencias a los elementos individuales en una matriz de enteros. A medida que las cadenas son objetos, puede hacer que las referencias a las cadenas individuales con sólo copiar las referencias en la matriz:

string[] s = new string[] { "one", "two", "three", "four", "five", "six" }; 
string[] sr = new string[] { s[0], s[1], s[2], s[3], s[4], s[5] }; 

Sin embargo, como las cadenas son objetos inmutables sólo puede utilizar las referencias para leer los artículos. Si asigna una cadena a una referencia en la matriz sr, sobrescribirá la referencia en lugar de cambiar el objeto al que apunta.

Si desea cambiar los objetos, tendrá que tener objetos mutables. Por ejemplo:

StringBuilder[] s = new StringBuilder[] { 
    new StringBuilder("one"), 
    new StringBuilder("two"), 
    new StringBuilder("three"), 
}; 
StringBuilder[] sr = new StringBuilder[] { s[0], s[1], s[2] }; 

Console.WriteLine(s[1]); // == "two" 
sr[1].Append(" point zero"); 
Console.WriteLine(s[1]); // == "two point zero" 
+0

Sí, esa es una forma interesante de resolverlo. No es exactamente lo que tenía en mente, sino una posible solución. –

0

Si alguien todavía está buscando una posible solución. Jugar poco con genéricos es posible si envuelve cada conjunto en clase para que pueda asignarse a otra instancia de la misma clase de ajuste haciendo referencia a un conjunto en él.

Pero esto debe usarse solo como prueba de concepto que es posible hacerlo. En general, no recomendaría el uso de esto, pero sugeriría rediseñar el código de una manera más eficiente. También vale la pena mencionar que simplemente puede asignar matriz a otra matriz como referencia a sus elementos (DOH).

Dicho esto aquí es el código de ejemplo de la matriz de referencia genérica a la matriz de datos:

using System; 
using System.Diagnostics; 

public class RefArray<TElem> 
{ 
    TElem[] data; 

    /// <summary>Initializes RefArray by creating Ref&lt;T>[]data from values.</summary> 
    public RefArray(TElem[] values) 
    { 
    data = values; 
    } 

    /// <summary>Creates reference RefArray pointing to same RefArray&lt;T> data.</summary> 
    public RefArray(RefArray<TElem> references) 
    { 
    this.data = references.data; 
    } 

    public int Length 
    { 
    get { return data.Length; } 
    } 

    public TElem this[int index] 
    { 
    get 
    { 
     return data[index]; 
    } 
    set 
    { 
     data[index] = value; 
    } 
    } 
} 

public static class RefArrayTest 
{ 

    public static void Usage() 
    { 

    // test ints (struct type) 
    RefArray<int> c = new RefArray<int>(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); 
    RefArray<int> d = new RefArray<int>(c); 
    Debug.Print(string.Format("c[3]: {0,-30}, d[3]: {1}", c[3], d[3])); 
    c[3] = 1111; 
    Debug.Print(string.Format("c[3]: {0,-30}, d[3]: {1}", c[3], d[3])); 
    d[3] = 2222; 
    Debug.Print(string.Format("c[3]: {0,-30}, d[3]: {1}", c[3], d[3])); 
    d[3] = c[3] + 3333; 
    Debug.Print(string.Format("c[3]: {0,-30}, d[3]: {1}", c[3], d[3])); 

    // test strings (class type) 
    RefArray<string> a = new RefArray<string>(new string[] { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" }); 
    RefArray<string> b = new RefArray<string>(a); 
    Debug.Print(string.Format("a[3]: {0,-30}, b[3]: {1}", a[3], b[3])); 
    a[3] = "set a"; 
    Debug.Print(string.Format("a[3]: {0,-30}, b[3]: {1}", a[3], b[3])); 
    b[3] = "set b"; 
    Debug.Print(string.Format("a[3]: {0,-30}, b[3]: {1}", a[3], b[3])); 
    a[3] = b[3] + " + take b set a"; 
    Debug.Print(string.Format("a[3]: {0,-30}, b[3]: {1}", a[3], b[3])); 

    // proof of no point since 
    string[] n1 = new string[] { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" }; 
    string[] n2 = n1; 
    Debug.Print(string.Format("n1[3]: {0,-30}, n2[3]: {1}", n1[3], n2[3])); 
    n1[3] = "set n1"; 
    Debug.Print(string.Format("n1[3]: {0,-30}, n2[3]: {1}", n1[3], n2[3])); 
    n2[3] = "set n2"; 
    Debug.Print(string.Format("n1[3]: {0,-30}, n2[3]: {1}", n1[3], n2[3])); 
    n1[3] = n2[3] + " + take n2 set n1"; 
    Debug.Print(string.Format("n1[3]: {0,-30}, n2[3]: {1}", n1[3], n2[3])); 
    } 

} 

Además, si los elementos mencionados tienen que estar fuera de servicio se podría añadir la clase Ref_T genérica para envolver cada valor como referencia:

using System; 
using System.Text; 
using System.Diagnostics; 

public class Ref_T<TValue> 
{ 
    public TValue data; 
    public Ref_T(TValue value) 
    { 
    this.data = value; 
    } 
} 

public class RefArray<TElem> 
{ 
    public readonly Ref_T<TElem>[] data; 

    /// <summary>Initializes RefArray by creating Ref&lt;T>[]data from values.</summary> 
    public RefArray(TElem[] values) 
    { 
    data = new Ref_T<TElem>[values.Length]; 
    for (int i = 0; i < values.Length; i++) 
    { 
     // creates reference members 
     data[i] = new Ref_T<TElem>(values[i]); 
    } 
    } 

    /// <summary>Creates reference RefArray pointing to same RefArray&lt;T> data.</summary> 
    public RefArray(RefArray<TElem> references) 
    { 
    data = references.data; 
    } 

    /// <summary>Creates reference RefArray pointing to same Ref&lt;T>[] references.</summary> 
    public RefArray(Ref_T<TElem>[] references) 
    { 
    data = references; 
    } 

    public int Length 
    { 
    get { return data.Length; } 
    } 

    public TElem this[int index] 
    { 
    get { return data[index].data; } 
    set { data[index].data = value; } 
    } 

    public override string ToString() 
    { 
    StringBuilder sb = new StringBuilder(); 
    int count = data.Length; 
    for (int i = 0; i < count; i++) 
     sb.Append(string.Format("[{0}]:{1,-4}, ", i, data[i].data)); 
    return sb.ToString(); 
    } 

    public static implicit operator Array(RefArray<TElem> a) 
    { 
    return a.data; 
    } 
} 

public static class RefArrayTest 
{ 

    public static void Usage() 
    {  
    // test ints (struct type) out of order 
    // initialize 
    RefArray<int> e = new RefArray<int>(new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }); 
    // reference e out of order 
    RefArray<int> f = new RefArray<int>(new Ref_T<int>[] 
     { e.data[8], e.data[6], e.data[4], e.data[2], e.data[0], 
     e.data[9], e.data[7], e.data[5], e.data[3], e.data[1] 
     }); 

    Debug.WriteLine("Initial: "); 
    Debug.WriteLine("e: [" + e + "]"); 
    Debug.WriteLine("f: [" + f + "]\r\n"); 

    e[3] = 1111; 
    Debug.WriteLine("e[3] = 1111;"); 
    Debug.WriteLine("e: [" + e + "]"); 
    Debug.WriteLine("f: [" + f + "]\r\n"); 

    f[3] = 2222; 
    Debug.WriteLine("f[3] = 2222;"); 
    Debug.WriteLine("e: [" + e + "]"); 
    Debug.WriteLine("f: [" + f + "]\r\n"); 

    f[3] = e[3] + 3333; 
    Debug.WriteLine("f[3] = e[3] + 3333;"); 
    Debug.WriteLine("e: [" + e + "]"); 
    Debug.WriteLine("f: [" + f + "]\r\n"); 

    Array.Reverse(f); 
    Debug.WriteLine("Array.Reverse(f);"); 
    Debug.WriteLine("e: [" + e + "]"); 
    Debug.WriteLine("f: [" + f + "]\r\n"); 
    } 

} 

salidas:

Initial: 
e: [[0]:0 , [1]:1 , [2]:2 , [3]:3 , [4]:4 , [5]:5 , [6]:6 , [7]:7 , [8]:8 , [9]:9 , ] 
f: [[0]:8 , [1]:6 , [2]:4 , [3]:2 , [4]:0 , [5]:9 , [6]:7 , [7]:5 , [8]:3 , [9]:1 , ] 

e[3] = 1111; 
e: [[0]:0 , [1]:1 , [2]:2 , [3]:1111, [4]:4 , [5]:5 , [6]:6 , [7]:7 , [8]:8 , [9]:9 , ] 
f: [[0]:8 , [1]:6 , [2]:4 , [3]:2 , [4]:0 , [5]:9 , [6]:7 , [7]:5 , [8]:1111, [9]:1 , ] 

f[3] = 2222; 
e: [[0]:0 , [1]:1 , [2]:2222, [3]:1111, [4]:4 , [5]:5 , [6]:6 , [7]:7 , [8]:8 , [9]:9 , ] 
f: [[0]:8 , [1]:6 , [2]:4 , [3]:2222, [4]:0 , [5]:9 , [6]:7 , [7]:5 , [8]:1111, [9]:1 , ] 

f[3] = e[3] + 3333; 
e: [[0]:0 , [1]:1 , [2]:4444, [3]:1111, [4]:4 , [5]:5 , [6]:6 , [7]:7 , [8]:8 , [9]:9 , ] 
f: [[0]:8 , [1]:6 , [2]:4 , [3]:4444, [4]:0 , [5]:9 , [6]:7 , [7]:5 , [8]:1111, [9]:1 , ] 

Array.Reverse(f); 
e: [[0]:0 , [1]:1 , [2]:4444, [3]:1111, [4]:4 , [5]:5 , [6]:6 , [7]:7 , [8]:8 , [9]:9 , ] 
f: [[0]:1 , [1]:1111, [2]:5 , [3]:7 , [4]:9 , [5]:0 , [6]:4444, [7]:4 , [8]:6 , [9]:8 , ] 

Espero que esto ayude a alguien.