C# 字典值引用类型 - 请解释为什么会这样

C# dictionary value reference type - please explain why this happens

我不明白以下 C# 中的 linqpad 查询的结果。评论应该解释我困惑的地方。

void Main()
{
    Dictionary<string, testClass> test = new Dictionary<string, testClass>();

    string key = "key";
    testClass val = null;

    test.Add(key, val);

    val = new testClass();

    test[key].Dump(); //returns null.   WHAT? I just set it!!!



    test[key] = val;
    val.Text = "something";
    //  returns val object, with Text set to "Something". 
    //  If the above didn't work, why does this work?
    test[key].Dump(); 



    val.Text = "Nothing";
    //  return val object, with Text set to "Nothing". 
    //  This, I expect, but, again, why didn't the first example work?
    test[key].Dump(); 



    val = null;
    //  returns val object, with Text set to "Nothing"...WHAT?? 
    //  Now my head is going to explode...
    test[key].Dump(); 

}

// Define other methods and classes here

public class testClass
{
    public override string ToString()
    {
        return Text;
    }

    public string Text { get; set;}     
}
test.Add(key, val);
val = new testClass();
test[key].Dump(); //returns null.   WHAT? I just set it!!!

您正在重新实例化 val。它不再指向您添加到 test 的同一个对象。当你新建一个引用对象时,它指向一个全新的对象。


如果,比如说,你在 testClass 上有一个 int 属性 并且做了:

var c = new testClass{ MyProperty = 1}
test.Add(key, c);

c.MyProperty = 2;
test[key].MyProperty.Dump();

看到2输出,因为你没有改变c指向的对象,但改变了属性 在现有对象上。

看下面的代码:

List<int> lst = new List<int>();
SomeFunc(lst);

这是 SomeFunc():

void SomeFunc(List<int> l)
{
  l = null;
}

调用SomeFunc()不会以任何方式改变变量lst的值。这是因为 l 获得了 lst 的副本。因此 lstl 都指向同一个内存对象,但它们本身是不同的,并且更改一个变量的内容不会影响另一个变量的值。

在某种程度上,您可以将引用类型视为经典指针,尽管它们在许多重要方面有所不同。有两个指针指向同一个对象并不意味着这两个指针对彼此的值有任何影响,并且将其中一个设置为不同的值(或 null)不会改变另一个的值。

您需要了解值类型和引用类型的区别。

在这种情况下,您有一个名为 val 的引用变量,它在内存中的对象地址为空。 当您调用 "test.Add(key, val);" 且 val 等于 null 时,则此引用变量后面有对象。 在下一行中,您初始化引用变量,现在它后面有一个对象。

所以这些结果绝对有意义。

查看此处了解更多信息: Value and Reference Types

C# 使用对象的引用类型。

1) 在第一个示例中,您添加了一对键和一个引用类型的值。引用指向 null 所以你实际上添加了 (key,null).

2) 现在您已经创建了一个对象的实例。 val 正在引用该对象,当您为它分配 test[key] = val 时,您实际上是在分配 val 正在引用的对象。

3)字典中引用的对象和val变量是一样的。所以你正在访问同一个对象。

4)您已将 val 设置为 null,因此它不会引用任何内容,但在字典中它仍然引用您创建的旧对象。

让我试着在代码注释中解释一下:

void Main()
{
    Dictionary<string, testClass> test = new Dictionary<string, testClass>();

    string key = "key";
    testClass val = null;
    //val now holds a null reference

    test.Add(key, val);
    //You add a null reference to the dictionary

    val = new testClass();
    //Now val holds a new reference for testClass, while the dictionary still has null

    test[key].Dump(); //returns null.   WHAT? I just set it!!!
    //Now it returns the null reference which you just added to the dictionary


    test[key] = val;
    //Now the dictionary holds the same reference as val

    val.Text = "something";
    //You set the Text of the val, and the dictionary-held value, because they're the same

    //  returns val object, with Text set to "Something". 
    //  If the above didn't work, why does this work?
    test[key].Dump(); 
    // I think now you know why..

    //...
}
testClass val = null;

test.Add(key, val);

val指的是null。您刚刚在 key 键下向字典添加了 null

val = new testClass();

您刚刚将 testClass 的一个新实例分配给名为 val 的变量。字典无法知道你这样做了。它不知道 val;你给它的只是 val 当时持有的价值。将其视为保留 val 当时持有的内容的副本。

如果你把“4”写在一张纸上,然后拍一张照片,然后擦掉它,写“5”,照片上还是“4”。

test[key].Dump(); //returns null.   WHAT? I just set it!!!

您在两行前将 test[key] 设置为 nulltest[key] 不是对 "whatever val refers to now" 的引用;它是您传递给 Dictionary.Add() 方法的任何值。该值为 null。字典会保留你给它的内容,直到你给它别的东西。

test[key] = val;

好的,现在你给了它别的东西。你给了它 val 的新值。

val.Text = "something";
//  returns val object, with Text set to "Something". 
//  If the above didn't work, why does this work?
test[key].Dump(); 

...这就是它起作用的原因。

val.Text = "Nothing";
//  return val object, with Text set to "Nothing". 
//  This, I expect, but, again, why didn't the first example work?
test[key].Dump(); 

字典仍然有 val 的新值——并且 val 仍然指的是同一个对象(把它想象成挂在 space 中,不在猎户座)。现在您已经更改了它们碰巧都引用的那个对象的其中一个属性。

val = null;

糟糕,它们不再指代同一个对象了。字典仍然引用那个对象,但是 val 现在什么都没有引用。

//  returns val object, with Text set to "Nothing"...WHAT?? 
//  Now my head is going to explode...
test[key].Dump(); 

你还没有给字典任何新的东西,所以它仍然有你最后给它的东西。

不要为此感到难过。去年我教我哥哥 C#,他是麻省理工学院电气工程博士。他在同一个问题上被狠狠地骂了一顿——早在 1995 年,他就是那个耐心地教指针如何在 C 中工作的人。

主要原因是变量(val)不是对象。它仅包含对对象(或 null)的引用。

testClass val = null 声明一个类型为 testClass 的变量并将其设置为 null。它不指向任何对象。

test.Add(key, val) 在字典中添加一个指向 null 的条目(注意:它 指向 val 也不指向任何对象)。

val = new testClass(); 创建 testClassnew 实例并且 val 现在指向该新对象。 test[key] 仍然是 null 并且没有指向任何对象。

test[key] = val;
val.Text = "something";
test[key].Dump(); 

此代码将 test[key] 指向 val 指向的同一对象。再次注意 是不是指向 val。当您使用 val.Text = "something" 更改对象时,您可以使用 test[key].Dump() 看到更改,因为它们都指向同一个对象。

val.Text = "Nothing";
test[key].Dump(); 

当你将val.Text设置为字符串"Nothing"时你可以通过test[key]看到变化,原因同上,它们都指向同一个对象.

val = null;
test[key].Dump(); 

此代码将 val 设置为指向 null。 test[key] 仍然 指向对象。现在 valtest[key] 指向不同的东西。