web-dev-qa-db-fra.com

Stocker une référence à un type de valeur?

J'écris un objet "Moniteur" pour faciliter le débogage de mon application. Cet objet Monitor est accessible au moment de l'exécution à partir d'un interpréteur IronPython. Ma question est la suivante: est-il possible en C # de stocker une référence à un type de valeur? Dites que j'ai la classe suivante:

class Test
{
    public int a;
}

Puis-je en quelque sorte stocker un "pointeur" sur "a" afin de pouvoir vérifier sa valeur à tout moment? Est-il possible d'utiliser du code sécurisé et géré?

Merci.

36
Alex Turpin

Vous ne pouvez pas stocker une référence à une variable dans un champ ou un tableau. Le CLR exige qu'une référence à une variable soit (1) un paramètre formel, (2) un local ou (3) le type de retour d'une méthode. C # prend en charge (1) mais pas les deux autres. 

(ASIDE: Il est possible pour que C # prenne en charge les deux autres; en fait, j’ai écrit un prototype de compilateur qui implémente ces fonctionnalités. C’est assez chouette. (Voir http://ericlippert.com/ 2011/06/23/ref-Returns-and-ref-Locals/ pour plus de détails.) Bien sûr, il faut écrire un algorithme qui vérifie qu'aucun ref local ne pourrait éventuellement faire référence à un local qui était en train d'être détruit. cadre de pile, ce qui devient un peu délicat, mais c'est faisable, peut-être que nous le prendrons en charge dans une future version hypothétique du langage. (MISE À JOUR: il a été ajouté à C # 7!))

Cependant, vous pouvez donner à une variable une durée de vie arbitrairement longue en la plaçant dans un champ ou un tableau. Si vous avez besoin d'une "référence" au sens de "J'ai besoin de stocker un alias dans une variable arbitraire", alors non. Mais si vous avez besoin d'une référence dans le sens de "J'ai besoin d'un jeton magique qui me permette de lire et d'écrire une variable particulière", utilisez simplement un délégué ou une paire de délégués.

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 locale externe x restera en vie au moins jusqu'à ce que rx soit récupéré.

51
Eric Lippert

Non - vous ne pouvez pas stocker un "pointeur" sur un type de valeur directement en C #.

En règle générale, vous tenez une référence à l'instance de test contenant "a" - cela vous donne accès à a (via testInstance.a).

9
Reed Copsey

Voici un motif que j'ai trouvé et que je me suis souvent servi. Habituellement, dans le cas de la transmission de propriétés en tant que paramètres à utiliser sur tout objet du type parent, mais cela fonctionne tout aussi bien pour une seule instance. (ne fonctionne pas pour les types de valeur de portée locale 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);
        }
    }
}

Cela peut alors être utilisé comme si

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;
}

Notez que l'interface avec un ensemble plus limité d'arguments de modèle, cela vous permet de passer le pointeur à quelque chose qui n'a aucune connaissance du type parent.

Vous pouvez même implémenter une autre interface sans argument de modèle qui appelle .ToString sur n'importe quel type de valeur (n'oubliez pas d'abord la vérification de null)

2
Spencer Rose

Vous pouvez littéralement prendre un pointeur sur un type de valeur à l'aide du code usafe

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
ja72