13

I have been searching for a way to save the references of variables of various types into a dictionary, together with a corresponding key. Then i would like to modify the instance of the variable by accessing its reference through the dictionary by its key. For storing the references, i tried to use <object>, but without success. Neither Dictionaries nor Lists accept anything like Dictionary<string, ref int>. The following code compiles, but seems to update the variables by value only. Any ideas or workarounds?

Here's the (tested) code:

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

And this is how i call the methods of the class

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"
Erik Funkenbusch
  • 92,674
  • 28
  • 195
  • 291
Thomas.Hut
  • 153
  • 1
  • 1
  • 6
  • You want to update the key of the dictionary? – Max Jun 20 '14 at 14:00
  • String is reference type, so you want to store a reference to a reference? – Peter Ritchie Jun 20 '14 at 14:01
  • For your `Test` class to be able to save a reference to your string/int and update it at a later time, it would have to pin the object in memory and use pointers. Don't do it. – dcastro Jun 20 '14 at 14:05
  • You are asking to store a pointer. No can do. A pointer is already ably wrapped in C#, it is a reference to an object. You need to store the *int* or the *string* as a member of the object. – Hans Passant Jun 20 '14 at 14:15
  • yes, i want to update the key. in C++ i would simply store the address of the variable. How about this, when I store the int or string as a member of Test1, can I then notify my calling class when their values change? – Thomas.Hut Jun 20 '14 at 14:18
  • @Thomas.Hut Then I will recommend you the following url: http://stackoverflow.com/questions/1937847/how-to-modify-key-in-a-dictionary-in-c-sharp – Max Jun 20 '14 at 14:19
  • @Max sorry, i was wrong. Of course i want to update the Value... – Thomas.Hut Jun 20 '14 at 14:24
  • @PeterRitchie Yes, exactly. Storing a reference to a reference is a very useful and powerful tool, and that's exactly what it sounds like he wants to be doing here. He just needs to be using a different tool than `ref` to create the second reference. – Servy Jun 20 '14 at 14:25
  • @Thomas.Hut Updating the key is impossible, the Value is possible. – Max Jun 20 '14 at 14:25
  • Maybe something with `WeakReference`... – Peter Ritchie Jun 20 '14 at 14:42
  • @PeterRitchie No, you're over thinking it. The easiest way to have a reference to a value is to use a `class`, as it's implementation is a reference to a value. – Servy Jun 20 '14 at 15:05
  • The proposed duplicate has nothing to do with this question... – Timwi Jun 20 '14 at 15:11

3 Answers3

14

The best solution I can think of for this is to store delegates in the dictionary that will allow you to retrieve and modify the variables.

Let’s start by declaring a type that contains a getter and a setter delegate:

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

The dictionary would have the type:

Dictionary<string, VariableReference>

To store a variable, say foo of type string, in the dictionary, you’d write the following:

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

To retrieve the value of a variable, you’d write

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

To change the value of a variable to newValue, you’d write

myDic[key].Set(newValue);

This way, the variable that you’re changing is genuinely the original variable foo, and foo can be anything (a local variable, a parameter, a field on an object, a static field... even a property).

Putting this all together, this is what the class Test1 would look like:

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"
Timwi
  • 65,159
  • 33
  • 165
  • 230
  • Thanks Timwi, this solved it. Just compiled your example and it works. – Thomas.Hut Jun 20 '14 at 14:50
  • 1
    While this works, it's a bit... complex. And it can be quite confusing as to how it works to people that don't understand how variable capture works in lambda's. Speaking of which, it seems like this may have problems with variable capture scoping if not used correctly... – Erik Funkenbusch Jun 20 '14 at 14:57
  • @ErikFunkenbusch: Capturing a variable doesn’t change its scope. I think the beauty in this solution is that you don’t really need to understand lambda variable capturing; it really Just Works... – Timwi Jun 20 '14 at 15:12
  • @Timwi - For instance, when using it in a loop, the getter may capture the value at the time of the first call, and each loop might use the same value over and over in some circumstances. – Erik Funkenbusch Jun 20 '14 at 15:45
  • 3
    @ErikFunkenbusch: No, that is not correct. Lambda expressions do not capture values. They capture the *variable*. – Timwi Jun 22 '14 at 14:43
  • 1
    Nice. I like it, but one thing I would do is make the delegate reference class generic with `VariableReference` replacing all ``s with ``s. That way you can restrict the type if you want. Then you can simply do `Dictionary>` later if you want to accept any type. – General Grievance Jul 25 '19 at 16:14
  • And it looks like someone thought my suggestion too: https://stackoverflow.com/questions/2256048/store-a-reference-to-a-value-type – General Grievance Sep 13 '21 at 19:47
3

Apart from the problem Kevin points out, you need to wrap your value types in some kind of reference type.

The problem, as you've figured out, is that generic types don't work with the ref keyword, and when you assign a new value type into your dictionary, it's replacing the reference with a different reference, not updating it. There is no way to retain the ref semantics once you assign it to the dictionary.

But, what you could do is something like this, simply wrap the value type in a reference type:

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
    }
}
Erik Funkenbusch
  • 92,674
  • 28
  • 195
  • 291
0

A ref parameter's reference can not leave the scope of the method that calls it. This is because the variable that is reference cannot be guaranteed to be in scope after the method call has finished. You need to use a tool other than ref to create a layer of indirection allowing a variable a caller is using to be mutated.

Doing this is quite easy though. You simply need a class with a mutable member:

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

You can now write:

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

Since you're now mutating a reference type that the caller also has a reference to, they can observe the change to its value.

Servy
  • 202,030
  • 26
  • 332
  • 449