在字典中存储对对象的引用

问题描述 投票:0回答:4

我一直在寻找一种方法将各种类型的变量的引用以及相应的键保存到字典中。然后我想通过字典的键访问它的引用来修改变量的实例。 为了存储参考文献,我尝试使用

<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"
c# object dictionary reference
4个回答
14
投票

我能想到的最好的解决方案是将委托存储在字典中,这样您就可以检索和修改变量。

让我们首先声明一个包含 getter 和 setter 委托的类型:

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"

3
投票

除了 Kevin 指出的问题之外,您还需要将值类型包装在某种引用类型中。

正如您所发现的,问题在于泛型类型不适用于 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 } }
    

0
投票

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

由于您现在正在改变调用者也有引用的引用类型,因此他们可以观察到其值的变化。


0
投票
您可以将 Dictionary

与 TValue[] 一起包装在实现 IDictionary 的类中。使用内部字典来跟踪数据在内部数组中的存储位置,并在需要时返回内部数组数据的引用。

这是一个示例实现:

class RefDictionary<TKey, TValue> : IDictionary<TKey, TValue> where TValue : struct { private Dictionary<TKey, int> indices = new(); private TValue[] values = Array.Empty<TValue>(); private Stack<int> freeIndices = new(); public ref TValue this[TKey key] { get => ref values[indices[key]]; } public Enumerator GetEnumerator() => new(this); IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); public void Add(KeyValuePair<TKey, TValue> item) { if (!freeIndices.TryPop(out var index)) { index = indices.Count; if (index >= values.Length) Array.Resize(ref values, Mathf.Max(2, Mathf.NextPowerOfTwo(index + 1))); } indices.Add(item.Key, index); values[index] = item.Value; } public void Clear() { Array.Clear(values, 0, indices.Count + freeIndices.Count); indices.Clear(); freeIndices.Clear(); } public bool Contains(KeyValuePair<TKey, TValue> item) { return indices.TryGetValue(item.Key, out var index) && EqualityComparer<TValue>.Default.Equals(values[index], item.Value); } public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) { foreach (var kv in indices) array[arrayIndex++] = new(kv.Key, values[kv.Value]); } public bool Remove(KeyValuePair<TKey, TValue> item) { if (indices.TryGetValue(item.Key, out var index) && EqualityComparer<TValue>.Default.Equals(values[index], item.Value)) { values[index] = default; indices.Remove(item.Key); freeIndices.Push(index); return true; } return false; } public int Count => indices.Count; public bool IsReadOnly => false; public void Add(TKey key, TValue value) { Add(new (key, value)); } public bool ContainsKey(TKey key) { return indices.ContainsKey(key); } public bool Remove(TKey key) { if (indices.TryGetValue(key, out var index)) { values[index] = default; indices.Remove(key); freeIndices.Push(index); return true; } return false; } public bool TryGetValue(TKey key, out TValue value) { if (indices.TryGetValue(key, out var index)) { value = values[index]; return true; } value = default; return false; } TValue IDictionary<TKey, TValue>.this[TKey key] { get => this[key]; set => this[key] = value; } public ICollection<TKey> Keys => indices.Keys; public ICollection<TValue> Values => values; public struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>> { private readonly RefDictionary<TKey, TValue> self; private Dictionary<TKey, int>.Enumerator e; public Enumerator(RefDictionary<TKey, TValue> self) { this.self = self; e = self.indices.GetEnumerator(); } public bool MoveNext() { return e.MoveNext(); } public void Reset() { } public KeyValuePair<TKey, TValue> Current => new(e.Current.Key, self.values[e.Current.Value]); object IEnumerator.Current => Current; public void Dispose() { e.Dispose(); } } }
    
© www.soinside.com 2019 - 2024. All rights reserved.