2010-06-05 5 views
24

Estoy tratando de entender cómo asignar por "referencia" a un campo de clase en C#.¿Cómo puedo asignar por "referencia" a un campo de clase en C#?

Tengo el siguiente ejemplo a considerar:

public class X 
{ 

    public X() 
    { 

    string example = "X"; 

    new Y(ref example); 

    new Z(ref example); 

    System.Diagnostics.Debug.WriteLine(example); 

    } 

} 

public class Y 
{ 

    public Y(ref string example) 
    { 
    example += " (Updated By Y)"; 
    } 

} 

public class Z 
{ 

    private string _Example; 

    public Z(ref string example) 
    { 

    this._Example = example; 

    this._Example += " (Updated By Z)"; 

    } 

} 

var x = new X(); 

Cuando se ejecuta el código por encima de la salida es:

X (Actualizado por Y)

Y no:

X (Actualizado Por Y) (Actualizado por Z)

Como esperaba.

Parece que la asignación de un "parámetro de referencia" a un campo pierde la referencia.

¿Hay alguna manera de mantener la suspensión de la referencia al asignar a un campo?

Gracias.

Respuesta

8

Nº ref es puramente una convención de llamada. No puedes usarlo para calificar un campo. En Z, _Example se establece en el valor de la referencia de cadena pasada. A continuación, asigne una nueva referencia de cadena utilizando + =. Nunca se asigna al ejemplo, por lo que la referencia no tiene efecto.

La única solución para lo que quiere es tener un objeto envoltorio mutable compartido (una matriz o un StringWrapper hipotético) que contiene la referencia (una cadena aquí). Generalmente, si lo necesita, puede encontrar un objeto mutable más grande para que las clases lo compartan.

public class StringWrapper 
{ 
    public string s; 
    public StringWrapper(string s) 
    { 
    this.s = s; 
    } 

    public string ToString() 
    { 
    return s; 
    } 
} 

public class X 
{ 
    public X() 
    { 
    StringWrapper example = new StringWrapper("X"); 
    new Z(example) 
    System.Diagnostics.Debug.WriteLine(example); 
    } 
} 

public class Z 
{ 
    private StringWrapper _Example; 
    public Z(StringWrapper example) 
    { 
    this._Example = example; 
    this._Example.s += " (Updated By Z)"; 
    } 
} 
+0

¿Te importaría ampliar un poco el segundo párrafo? "un objeto mutable más grande" no está del todo claro para mí. Además, ¿cómo funcionaría un StringWrapper si no hay forma de mantener la referencia de cadena en un campo? –

+0

He dado un ejemplo que debería aclarar. Como dije, en un diseño real, StringWrapper probablemente sea un objeto comercial que contenga algo más que una cadena. –

+0

Gracias por la idea. Mi ejemplo probablemente fue más simplificado porque realmente tengo un objeto (no una cadena) en mi código real que estoy tratando de asignar por referencia. El comportamiento que estoy buscando es permitir que un usuario de un objeto pueda asignar nulo a dicho objeto, o incluso asignar una nueva instancia de ese tipo desde sus métodos. Paso el objeto a través del constructor como parte de una inyección de dependencia y, por lo tanto, los otros métodos solo pueden ver el objeto al acceder a un campo. Parece que lo que quiero no se puede lograr, lo cual es una pena. – Jamie

3

que se olvidó de actualizar la referencia en la clase Z:

public class Z { 
    private string _Example; 

    public Z(ref string example) { 
     example = this._Example += " (Updated By Z)"; 
    } 
} 

Salida: X (Actualizado por Y) (Actualizado por Z)

punto a tener en cuenta es que el + = operador para una cadena llama al método String.Concat(). Lo cual crea un nuevo objeto de cadena , no actualiza el valor de una cadena. Los objetos de cadena son inmutables, la clase de cadena no tiene ningún método o campo que le permita cambiar el valor. Muy diferente del comportamiento predeterminado de un tipo de referencia regular.

lo tanto, si se utiliza un método de cadena o el operador, siempre hay que asignar el valor devuelto de nuevo a una variable. Esta es una sintaxis bastante natural, los tipos de valores se comportan de la misma manera. Su código sería muy similar si utilizó una int en lugar de una cadena.

+1

Gracias por la idea Hans. Mi ejemplo tiene la culpa: para hacerlo simple, hice la actualización en el constructor. En realidad, el campo se establecería en el constructor, pero la actualización se realizará en otro método después de la instancia del objeto. – Jamie

56

Como otros han señalado, no se puede tener un campo de tipo "árbitro variable". Sin embargo, el solo hecho de saber que no puedes hacerlo es probablemente insatisfactorio; probablemente también quiera saber primero, por qué no, y segundo, cómo evitar esta restricción.

La razón es porque sólo hay tres posibilidades:

1) No permitir campos de tipo ref

2) Permitir campos peligrosos de tipo ref

3) No utilizar el almacenamiento temporal grupo de variables locales (también conocido como "la pila")

Supongamos que permitimos los campos del tipo de referencia. Posteriormente, se podría hacer

public ref int x; 
void M() 
{ 
    int y = 123; 
    this.x = ref y; 
} 

y ahora y se puede acceder después M ultima. Esto significa que o bien estamos en caso (2): el acceso al this.x se bloqueará y morirá horriblemente porque el almacenamiento de y ya no existe, o estamos en el caso (3), y el y local se almacena en la basura montón acumulado, no el grupo de memoria temporal.

Nos gusta la optimización de que las variables locales se almacenen en el grupo temporal incluso si están siendo aprobadas por ref, y odiamos la idea de que pueda dejar una bomba de tiempo que pueda hacer que su programa falle y muera más tarde. Por lo tanto, la opción uno es: sin campos de ref.

Tenga en cuenta que para las variables locales que son variables cerradas de funciones anónimas elegimos la opción (3); esas variables locales no están asignadas fuera del grupo temporal.

Lo que nos lleva a la segunda pregunta: ¿cómo lo solucionamos? Si la razón por la que desea que un campo ref es hacer un getter y setter de otra variable, eso es perfectamente legal:

sealed class Ref<T> 
{ 
    private readonly Func<T> getter; 
    private readonly Action<T> setter; 
    public Ref(Func<T> getter, Action<T> setter) 
    { 
     this.getter = getter; 
     this.setter = setter; 
    } 
    public T Value { get { return getter(); } set { setter(value); } } 
} 
... 
Ref<int> x; 
void M() 
{ 
    int y = 123; 
    x = new Ref<int>(()=>y, z=>{y=z;}); 
    x.Value = 456; 
    Console.WriteLine(y); // 456 -- setting x.Value changes y. 
} 

y ahí lo tienes. y se almacena en el montón de gc, y x es un objeto que tiene la capacidad de obtener y establecer y.

Tenga en cuenta que el CLR admite los ref locales y los métodos de devolución de ref, aunque C# no. Quizás una versión futura hipotética de C# respalde estas características; Lo prototipé y funciona bien. Sin embargo, esto no es muy importante en la lista de prioridades, así que no me gustaría tener mis esperanzas.

ACTUALIZACIÓN: La característica mencionada en el párrafo anterior finalmente se implementó de manera real en C# 7. Sin embargo, todavía no puede almacenar una referencia en un campo.

+0

¿Por qué se requieren el captador y el colocador? ¿Es importante el cierre para que Y permanezca en caja?¿Sería posible usar simplemente Ref {public T Value {get; conjunto; }} en su lugar? –

+1

@ChrisMoschini: Claro, eso sería perfectamente posible. Pero luego está pasando una referencia a * un valor *, y no * a una variable *. El objetivo de la pregunta es "¿cómo puedo almacenar una referencia a una variable?" Supongamos que en lugar de la variable local y, desea almacenar una "referencia" al 12 ° elemento de una matriz, de modo que la mutación de la referencia mute el 12 ° elemento de una matriz. Su tipo propuesto no ayudaría en ese escenario. –

+0

Es cierto, aunque ninguno pasaría .someMethod (ref array [12]), que parece ser más acorde con el espíritu de la pregunta. Ninguna de nuestras propuestas le ofrece un tipo de valor que está enmarcado y permanece en el recuadro, desafortunadamente. Nullable en realidad podría ser la coincidencia más cercana a lo que el asker estaba intentando. –

Cuestiones relacionadas