Хранить ссылку на объект в словаре

Я искал способ сохранить ссылки на переменные различных типов в словаре вместе с соответствующим ключом. Затем я хотел бы изменить экземпляр переменной, обратившись к ее ссылке через словарь по ее ключу. Для хранения ссылок, я попытался использовать <object>, но без успеха. Ни словари, ни списки не принимают ничего подобного Dictionary<string, ref int>. Следующий код компилируется, но, похоже, обновляет переменные только по значению. Любые идеи или обходные пути?

вот (проверенный) код:

class Test1
{
    IDictionary<string, object> MyDict = new Dictionary<string, object>();

    public void saveVar(string key, ref int v) //storing the ref to an int
    {
        MyDict.Add(key, v);
    }
    public void saveVar(string key, ref string s) //storing the ref to a string
    {
        MyDict.Add(key, s);
    }

    public void changeVar(string key) //changing any of them
    {
        if(MyDict.GetType() == typeof(int))
        {
            MyDict[key] = (int)MyDict[key] * 2;
        }
        if(MyDict.GetType() == typeof(string))
        {
            MyDict[key] = "Hello";
        }
    }
}

и вот как я называю методы класса

Test1 t1 = new Test1();
int myInt = 3;
string myString = "defaultString";

Console.WriteLine(myInt); //returns "3"
Console.WriteLine(myString); //returns "defaultString"

t1.saveVar("key1", ref myInt);
t1.saveVar("key2", ref myString);

t1.changeVar("key1");
t1.changeVar("key2");

Console.WriteLine(myInt); //should return "6"
Console.WriteLine(myString); //should return "Hello"

3 ответов


лучшее решение, которое я могу придумать для этого, - это хранить делегаты в словаре, который позволит вам извлекать и изменять переменные.

давайте начнем с объявления типа, который содержит геттер и делегат сеттера:

sealed class VariableReference
{
    public Func<object> Get { get; private set; }
    public Action<object> Set { get; private set; }
    public VariableReference(Func<object> getter, Action<object> setter)
    {
        Get = getter;
        Set = setter;
    }
}

словарь будет иметь вид:

Dictionary<string, VariableReference>

чтобы сохранить переменную, скажем foo типа string, в словаре вы напишете следующее:

myDic.Add(key, new VariableReference(
    () => foo,                      // getter
    val => { foo = (string) val; }  // setter
));

для получения стоимостью переменной, вы бы писать

var value = myDic[key].Get();

до изменить значение переменной newValue, ты вот пишешь

myDic[key].Set(newValue);

таким образом, переменная, которую вы меняете, действительно является исходной переменной foo и foo может быть что угодно (локальная переменная, параметр, поле объекта, статическое поле... даже свойства).

положить все это вместе, это то, что класс Test1 будет посмотрите, как:

class Test1
{
    Dictionary<string, VariableReference> MyDict = new Dictionary<string, VariableReference>();

    public void saveVar(string key, Func<object> getter, Action<object> setter)
    {
        MyDict.Add(key, new VariableReference(getter, setter));
    }

    public void changeVar(string key) // changing any of them
    {
        if (MyDict[key].Get() is int)
        {
            MyDict[key].Set((int)MyDict[key].Get() * 2);
        }
        else if (MyDict[key].Get() is string)
        {
            MyDict[key].Set("Hello");
        }
    }
}

// ...

Test1 t1 = new Test1();
int myInt = 3;
string myString = "defaultString";

Console.WriteLine(myInt);    // prints "3"
Console.WriteLine(myString); // prints "defaultString"

t1.saveVar("key1", () => myInt, v => { myInt = (int) v; });
t1.saveVar("key2", () => myString, v => { myString = (string) v; });

t1.changeVar("key1");
t1.changeVar("key2");

Console.WriteLine(myInt);    // actually prints "6"
Console.WriteLine(myString); // actually prints "Hello"

A ref ссылка параметра не может покидать область действия вызывающего его метода. Это происходит потому, что переменная, которая является ссылкой, не может быть гарантирована в области после завершения вызова метода. Вам нужно использовать инструмент, отличный от ref чтобы создать слой косвенности, позволяющий переменной, которую использует вызывающий объект, мутировать.

сделать это очень просто. Вам просто нужен класс с изменяемым членом:

public class Pointer
{
    public object Value { get; set; }
}

теперь вы можете пиши:

class Test1
{
    IDictionary<string, Pointer> MyDict = new Dictionary<string, Pointer>();

    public void saveVar(string key, Pointer pointer) //storing the ref to an int
    {
        MyDict.Add(key, pointer);
    }

    public void changeVar(string key) //changing any of them
    {
        if (MyDict[key].Value.GetType() == typeof(int))
        {
            MyDict[key].Value = (int)(MyDict[key].Value) * 2;
        }
        if (MyDict[key].Value.GetType() == typeof(string))
        {
            MyDict[key].Value = "Hello";
        }
    }
}

поскольку теперь вы мутируете ссылочный тип, на который также есть ссылка вызывающего абонента, они могут наблюдать изменение его значения.


помимо проблемы, на которую указывает Кевин, вам нужно обернуть типы значений в какой-то ссылочный тип.

проблема, как вы выяснили, заключается в том, что общие типы не работают с ключевым словом ref, и когда вы назначаете новый тип значения в свой словарь, он заменяет ссылку другой ссылкой, а не обновляет ее. Невозможно сохранить семантику ref после ее назначения словарю.

но то, что вы могли бы сделать, это что-то например, просто оберните тип значения в ссылочный тип:

public class MyRef<T> {
    public T Ref {get;set;}
}

public class Test1
{
    Dictionary<string, object> MyDict = new Dictionary<string, object>();

    public void saveVar(string key, object v) 
    {
        MyDict.Add(key, v);
    }

    public void changeVar(string key, object newValue) //changing any of them
    {
        var ref1 = MyDict[key] as MyRef<int>;
        if (ref1 != null) {
            ref1.Ref = (int)newValue;
            return; // no sense in wasting cpu cycles
        }

        var ref2 = MyDict[key] as MyRef<string>;
        if (ref2 != null) {
            ref2.Ref = newValue.ToString();
        }
    }

    public void DoIt()
    {
        var v = new MyRef<int> { Ref = 1 };

        saveVar("First", v);
        changeVar("First", 2);

        Console.WriteLine(v.Ref.ToString()); // Should print 2
        Console.WriteLine(((MyRef<int>)MyDict["First"]).Ref.ToString()); // should also print 2
    }
}