【问题标题】:Struct containing reference types包含引用类型的结构
【发布时间】:2012-02-08 23:59:49
【问题描述】:

一个结构是一个值类型,所以如果我将一个结构分配给另一个结构,它的字段将被复制到第二个结构中。但是,如果结构的某些字段是引用类型会发生什么?

public struct MyIPEndPoint
{
    public String IP;
    public UInt16 Port;

    public MyIPEndPoint(String ipAddress, UInt16 portNumber)
    {
        IP = ipAddress;
        Port = portNumber;
    }

    public override string ToString()
    {
        return IP+":"+Port;
    }
}

...

static int Main(string[] args)
{
    MyIPEndPoint address1 = new MyIPEndPoint("127.0.0.1", 8080);
    MyIPEndPoint address2 = address1;

    address2.IP = "255.255.255.255";
    address2.Port = 9090;

    Console.WriteLine(address1);
    Console.WriteLine(address2);
}

输出是:

127.0.0.1:8080
255.255.255.255:9090

为什么address1IP(字符串,即引用类型)不会改变? 如果我将string 替换为IPAddress 以表示MyIPEndPoint 中的IP,则会发生相同的行为:尽管IPAddress 是一个类(即引用类型),但它不会作为引用类型。为什么?

确实,如果我使用新的简单类 MyIP 包装代表 IP 的 string,则行为会发生变化。

public class MyIP
{
    public string IpAsString;

    public MyIP(string s)
    {
        IpAsString = s;
    }
    public override string ToString()
    {
        return IpAsString;
    }
}

当然你也应该通过以下方式调整MyIPEndPoint结构:

public struct MyIPEndPoint
{
    public MyIP IP;   // modification
    public UInt16 Port;

    public MyIPEndPoint(String ipAddress, UInt16 portNumber)
    {
        IP = new MyIP(ipAddress);   // modification
        Port = portNumber;
    }

    public override string ToString()
    {
        return IP+":"+Port;
    }
}

最后在Main我只改了一句:

MyIPEndPoint address1 = new MyIPEndPoint("127.0.0.1", 8080);
MyIPEndPoint address2 = address1;

address2.IP.IpAsString = "255.255.255.255";   // modification
address2.Port = 9090;

Console.WriteLine(address1);
Console.WriteLine(address2);

现在的输出是:

255.255.255.255:8080
255.255.255.255:9090

我在第一种情况下期待这个输出。 为什么在第一种情况下,引用的行为不符合预期

【问题讨论】:

    标签: c# .net


    【解决方案1】:

    考虑您的第一个示例。您有两个抽屉,分别标有“地址一”和“地址二”。两个抽屉都是空的。

    MyIPEndPoint address1 = new MyIPEndPoint("127.0.0.1", 8080);     
    

    现在你得到一张纸,在纸上写下“127.0.0.1, 8080”,然后把它放在抽屉 1。

    MyIPEndPoint address2 = address1;      
    

    现在你用复印机把“地址一”抽屉里的纸复印一下,然后把复印件放到“地址二抽屉”里。

    address2.IP = "255.255.255.255";     
    address2.Port = 9090; 
    

    现在你把地址二抽屉里的纸拿出来,划掉里面的东西,然后用新的文字替换它。

    一号抽屉里的纸没有变。还是和以前一样。

    现在考虑您的第二个示例。现在你有两个空抽屉,和以前一样,还有一本书空白纸。

    MyIPEndPoint address1 = new MyIPEndPoint("127.0.0.1", 8080); 
    

    您随机选择一本书并将其命名为“REFERENCE ONE”。在那个页面上你写“127.0.0.1”。你拿一张松散的纸,在纸上写下“REFERENCE ONE, 8080”,然后把它贴在标有“address one”的抽屉里。

    MyIPEndPoint address2 = address1;  
    

    你把“地址一”的论文复印一下,把复印件放到“地址二”。

    address2.IP.IpAsString = "255.255.255.255"; 
    

    您打开抽屉“地址二”,看到上面写着“参考一号”。你翻阅这本书,直到找到一页写着“REFERENCE ONE”。你划掉那里的内容并用新文本替换它。

    address2.Port = 9090;  
    

    您打开抽屉“地址二”并刮掉“8080”并将其替换为“9090”。您将 REFERENCE ONE 留在原处。

    现在完成后,抽屉“地址一”包含“REFERENCE ONE,8080”,抽屉“地址二”包含“REFERENCE ONE,9090”,并且该书有一页显示“REFERENCE ONE:255.255。 255.255"。

    现在你明白引用类型和值类型的区别了吗?

    【讨论】:

    • 是的,这个例子帮助我更好地理解了引用类型和值类型之间的区别!
    【解决方案2】:

    您已经正确理解,对于结构,address1 和 address2 不是同一个对象。值已被复制。但是,对于该领域,这是一个简单的重新分配案例。它与字符串是引用类型或任何特殊规则或任何不变性建议无关。您只是用另一个值重新分配了一个属性或字段。

    someStruct.SomeString = "A";
    anotherStruct = someStruct;
    anotherStruct.SomeString = "B"; // would never affect someStruct
    

    您已覆盖此示例中的引用。在短时间内,两个结构的字段都包含相同的引用这一事实并不重要。在你的第二个例子中,你做了一些非常不同的事情。

    someStruct.IP.SomeString = "A";
    anotherStruct = someStruct;
    anotherStruct.IP.SomeString = "B"; 
    

    在这种情况下,IP 的值没有改变。 IP 的部分状态 已更改。每个结构的字段仍然引用相同的 IP。

    简单点

    var foo = new Foo(); // Foo is class
    var other = foo; 
    // other and foo contain same value, a reference to an object of type Foo
    other = new Foo(); // was foo modified? no! 
    
    int x = 1;
    int y = x;
    y = 2; // was x modified? of course not.
    
    string s = "S";
    string t = s;
    t = "T"; // is s "T"? (again, no)
    

    变量和字段保存值。对于类,这些值是对对象的引用。两个变量或字段可以拥有相同的引用,但这并不意味着这些变量本身是链接的。它们无论如何都没有联系,它们只是拥有一个共同的价值。当您替换一个变量或字段的值时,另一个变量不受影响。


    没有关于特定主题,但值得注意的是,可变结构被许多人视为邪恶。其他人并不完全持有相同的观点,或者至少不那么虔诚。 (但是,值得注意的是, Address 是一个类,那么 address1 和 address2 将保持相同的值(对 Address 对象的引用),并修改为只要 address1 或 address2 本身都没有被重新分配,address1 的 state 将通过 address2 可见。)

    如果这是您的代码的实际表示,则值得对 mutable structs 进行一些研究,这样您至少可以充分了解您可能遇到的各种陷阱。

    【讨论】:

    • 我理解原因:换句话说,如果 IPAddress 的字段是公开的,我可以重现相同的结果。
    • 如果您可以修改字符串变量(或字段)引用的对象的状态而不实际覆盖引用,您可以观察到两个结构的变化。但是您没有修改状态(并且您不能,至少不能在安全代码中),您通过重新分配完全覆盖了引用。在第二个中,您修改了两个结构实例引用的对象的状态。 IP 的值,对对象的引用,没有改变,所以你观察了两者的结果。
    • 我不确定您的陈述是否属实,“这与字符串是引用类型或任何特殊规则或任何不变性建议无关。”如果字符串是传统的引用类型,那么通过一个引用更改值将更改所有引用的值。但是 string 是不可变的引用类型,这意味着它的行为类似于值类型。 stackoverflow.com/questions/636932/…。如果您假设“=”始终意味着分配引用,那么您的陈述是正确的,但对于某些人来说,它意味着复制值。
    • @BlueMonkMN,一个字符串一个常规的引用类型。不要将简单的相等检查和文字赋值支持误认为就重新赋值而言,它的行为也不像其他引用类型(或任何类型)。 “=”表示赋值,无论您是在谈论值类型还是引用类型。不同的是,对于引用类型,值就是引用。但是无论你是在说int、string,还是你自己自定义的可变类Foo,当你使用“=”赋值给变量时,您正在用新值覆盖该变量。
    • "如果一个字符串是一种传统的引用类型,那么通过一个引用改变值将会改变所有引用。"再一次,让我们清楚一点。如果您有Foo a = new Foo(); a.Bar = 1; Foo b = a; a = new Foo(); a.Bar = 2;,则a 的重新分配和变异b 毫无关系。 “更改引用类型的值”不应与“更改由变量引用的 对象 的状态”混淆。字符串的行为类似于此处的任何其他值或引用类型。对“不变性”的归因只会掩盖极其简单的重新分配问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-11-05
    • 2014-01-22
    • 1970-01-01
    • 2013-01-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多