包含引用类型的Struct

时间:2012-02-08 23:59:49

标签: c# .net

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

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

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

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

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

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

2 个答案:

答案 0 :(得分:27)

考虑你的第一个例子。你有两个抽屉,标有“地址一”和“地址二”。两个抽屉都是空的。

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”,然后把它贴在标有“地址一”的抽屉里。

MyIPEndPoint address2 = address1;  

您在“地址一”中复印了该纸张,并将该副本放入“地址二”。

address2.IP.IpAsString = "255.255.255.255"; 

你打开抽屉“地址二”,看到它说“REFERENCE ONE”。你仔细阅读这本书,直到找到一个名为“REFERENCE ONE”的页面。你划掉那里的东西并用新文本替换它。

address2.Port = 9090;  

打开抽屉“地址二”并划出“8080”并将其替换为“9090”。你把REFERENCE ONE留在原处。

现在当你完成后,抽屉“地址一”包含“参考一个,8080”,抽屉“地址二”包含“参考一个,9090”,书中有一个页面上写着“参考一个:255.255”。 255.255" 。

现在您了解参考和价值类型之间的区别吗?

答案 1 :(得分:19)

您已正确理解,对于结构,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状态的一部分已更改。每个struct的字段仍然引用相同的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)

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


不是针对特定主题,但值得注意的是,可变结构被许多人视为邪恶。其他人并不完全持有同样的观点,或者至少不是虔诚的观点。 (但是,值得注意已经地址是一个类,那么address1和address2将保持相同的值(对Address对象的引用),并修改为只要地址1或地址2都没有被重新分配,地址1的状态就可以通过地址2 看到。

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