2010-02-13 13 views
36

Estoy escribiendo un objeto "Monitor" para facilitar la depuración de mi aplicación. Se puede acceder a este objeto Monitor en tiempo de ejecución desde un intérprete IronPython. Mi pregunta es, ¿es posible en C# almacenar una referencia a un tipo de valor? Decir que tengo la clase siguiente:¿Almacenar una referencia a un tipo de valor?

class Test 
{ 
    public int a; 
} 

¿hay algún modo almacenar un "puntero" a "a" con el fin de poder comprobar su valor en cualquier momento? ¿Es posible usar un código seguro y administrado?

Gracias.

Respuesta

47

No puede almacenar una referencia a una variable en un campo o matriz. El CLR requiere que una referencia a una variable esté en (1) un parámetro formal, (2) un local, o (3) el tipo de retorno de un método. C# admite (1) pero no los otros dos.

(aparte: Es posible para C# para apoyar a los otros dos, de hecho he escrito un compilador prototipo que hace poner en práctica esas características es bastante limpio (Ver http://ericlippert.com/2011/06/23/ref-returns-and-ref-locals/ para más detalles) Por supuesto, uno tiene que... escriba un algoritmo que verifique que ningún ref local podría estar refiriéndose a un local que estaba en un marco de pila ahora destruido, lo que se torna un poco complicado, pero es factible. Quizás respaldemos esto en una versión futura hipotética del lenguaje. (ACTUALIZACIÓN: ¡Se agregó a C# 7!))

Sin embargo, puede hacer que una variable tenga una duración arbitrariamente larga, al ponerla en un campo o matriz. Si lo que necesita es una "referencia" en el sentido de "Necesito almacenar un alias para una variable arbitraria", entonces, no. Pero si lo que necesita es una referencia en el sentido de "Necesito un token mágico que me permita leer y escribir una variable en particular", simplemente use un delegado o un par de delegados.

sealed class Ref<T> 
{ 
    private Func<T> getter; 
    private 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<string> M() 
{ 
    string x = "hello"; 
    Ref<string> rx = new Ref<string>(()=>x, v=>{x=v;}); 
    rx.Value = "goodbye"; 
    Console.WriteLine(x); // goodbye 
    return rx; 
} 

La variable local externa x permanecerá activa al menos hasta que se reclame rx.

+0

Para que esto funcione correctamente, el lenguaje/marco debe permitir que las ubicaciones de almacenamiento se declaren persistentes, retornables o efímeras, y solo permite guardar cosas de una clase de almacenamiento en una clase igual o más restrictiva. El valor de retorno de una función sería el más restrictivo de su clase de devolución declarada, o la clase de almacenamiento más restrictiva pasó a sus parámetros "retornables". Dichas reglas no serían útiles no solo con los parámetros 'ref', sino también con otros parámetros (de modo que el código podría pasar una referencia a un objeto de clase mutable a un método, y saber ... – supercat

+0

... que cualquier mutación que pudiera cualquier resultado de eso estaría completo antes de que el método en cuestión regresara, por lo tanto, en muchas situaciones eliminando la necesidad de una copia defensiva). – supercat

+0

+1 tu respuesta porque resuelve el problema de persistencia. Sin embargo, estoy un poco triste porque C# no tiene ningún mecanismo para almacenar los tipos de valores como referencia. Entiendo que esto podría complicar las cosas para el GC, pero hacer referencia a los tipos de valores realmente podría tener un gran impacto en las técnicas de codificación. –

8

No, no puede almacenar un "puntero" a un tipo de valor directamente en C#.

Normalmente, debe mantener una referencia a la instancia de Prueba que contiene "a" - esto le da acceso a a (a través de testInstance.a).

+0

Pero supongo que no hay forma de almacenar dinámicamente algo? ¿Como envolverlo en un delegado o tal? –

+0

Siempre puede copiarlo. Puede hacer un cierre, pero eso (indirectamente) almacena una referencia a la clase contenedora ... –

+1

He estado buscando en toda la red cómo hacerlo. ¿Sería tan amable de editar su respuesta mostrando un pequeño ejemplo, por favor? Por ahora estoy usando Reflection, pero me da cosas como Monitor.Watch (new Property (Game, "FPS")), prefiero solo hacer Monitor.Watch (Game.FPS) o algo más simple al menos . –

-3
class Test 
{ 
    private int a; 

    /// <summary> 
    /// points to my variable type interger, 
    /// where the identifier is named 'a'. 
    /// </summary> 
    public int A 
    { 
     get { return a; } 
     set { a = value; } 
    } 
} 

¿Por qué pasar por todas esas complicaciones de escribir código complicado, declarando identificadores en todas partes que enlazan a la misma ubicación? Cree una propiedad, agregue código XML para ayudarlo fuera de la clase y use las propiedades en su codificación.

No sé sobre el almacenamiento de un puntero, no creo que sea posible, pero si solo quiere comprobar su valor, la forma más segura que conozco es crear una propiedad de la variable. Al menos de esa manera puede verificar su propiedad en cualquier momento y si la variable es estática, ni siquiera tendrá que crear una instancia de la clase para acceder a la variable.

Las propiedades tienen muchas ventajas; tipo de seguridad es uno, XML etiqueta otro. Comience a usarlos!

+2

Por favor absténgase de usar lenguaje obsceno en su respuesta. Además de emoticones. Lea las [preguntas frecuentes] (http://stackoverflow.com/faq) para obtener instrucciones. – ekims

+2

Esto no parece responder a la pregunta. La pregunta planteada incluye una variable pública, que es casi la misma que la propiedad pública que sugiere. –

+0

emoticones son habbit de clientes de chat. no estoy seguro de lo que quiere decir con blasfemia, solo estoy expresando mis pensamientos. por lo que entiendo, él quiere un identificador que apunta a la variable, si fuera posible, él tendría que (en mi conocimiento) hacer pública la variable o el elemento que accede a ella. Estoy diciendo, está bien, ¿por qué no usar una propiedad? al crear una instancia de su clase 'monitor' dentro de MAIN o en cualquier otro lugar, use esa propiedad para verificar su valor. O hazlo estático y accede a esa propiedad de esta manera; namespace.class.property, cuando necesita verificar el valor. una alternativa. – Gorlykio

2

Este es un patrón que se me ocurrió que me encuentro usando bastante. Por lo general, en el caso de pasar propiedades como parámetros para usar en cualquier objeto del tipo padre, pero funciona igual de bien para una sola instancia.(No funciona para los tipos de valor alcance locales Tho)

public interface IValuePointer<T> 
{ 
    T Value { get; set; } 
} 
public class ValuePointer<TParent, TType> : IValuePointer<TType> 
{ 
    private readonly TParent _instance; 
    private readonly Func<TParent, TType> _propertyExpression; 
    private readonly PropertyInfo _propInfo; 
    private readonly FieldInfo _fieldInfo; 

    public ValuePointer(TParent instance, 
         Expression<Func<TParent, TType>> propertyExpression) 
    { 
     _instance = instance; 
     _propertyExpression = propertyExpression.Compile(); 
     _propInfo = ((MemberExpression)(propertyExpression).Body).Member as PropertyInfo; 
     _fieldInfo = ((MemberExpression)(propertyExpression).Body).Member as FieldInfo; 
    } 

    public TType Value 
    { 
     get { return _propertyExpression.Invoke(_instance); } 
     set 
     { 
      if (_fieldInfo != null) 
      { 
       _fieldInfo.SetValue(_instance, value); 
       return; 
      } 
      _propInfo.SetValue(_instance, value, null); 
     } 
    } 
} 

Esto entonces se puede utilizar como tal

class Test 
{ 
    public int a; 
} 
void Main() 
{ 
    Test testInstance = new Test(); 
    var pointer = new ValuePointer(testInstance,x=> x.a); 
    testInstance.a = 5; 
    int copyOfValue = pointer.Value; 
    pointer.Value = 6; 
} 

Aviso la interfaz con un conjunto más limitado de argumentos de plantilla, esto le permite pasar el puntero a algo que no tiene conocimiento del tipo padre.

Incluso se podría implementar otra interfaz sin argumentos plantilla que llama .ToString en cualquier tipo de valor (no se olvide el cheque nulo primero)

0

literalmente puede tomar un puntero a un tipo de valor usando usafe código

public class Foo 
{ 
    public int a; 
} 

unsafe static class Program 
{ 
    static void Main(string[] args) 
    { 
     var f=new Foo() { a=1 }; 
     // f.a = 1 

     fixed(int* ptr=&f.a) 
     { 
      *ptr=2; 
     } 
     // f.a = 2 
    } 
} 
+0

Esto solo funciona para tipos numéricos intrínsecos. CLR no permite los tipos 'Foo *'. – ja72

Cuestiones relacionadas