2012-02-08 2 views
23

Структура - тип значения, поэтому, если я назначу структуру другой структуре, ее поля будут скопированы во второй структуре. Но что произойдет, если некоторые поля структуры являются ссылочным типом?Структуры, содержащие ссылочные типы

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, чтобы представить IP в пределах MyIPEndPoint: хотя 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 

Я ожидал этот выход в первом случае. Почему в первом случае ссылка не ведет себя как ожидалось?

ответ

17

Вы правильно поняли, что с structs адрес1 и 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-адрес.

Put в более простых терминах

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) 

Переменные и поля содержат значения. Для классов эти значения являются ссылками на объекты. Две переменные или поля могут содержать одну и ту же ссылку, но это не означает, что эти переменные сами связаны. Во всяком случае, они не связаны, они просто имеют общую ценность. Когда вы заменяете значение для одной переменной или поля, другая переменная не изменяется.


Не на конкретной теме, но стоит отметить, что изменяемые структуры являются рассматривается многими как зла. Другие не совсем придерживаются того же взгляда, или, по крайней мере, не так религиозно. (Тем не менее, также стоит отметить, что имеет Адрес был классом, тогда адрес1 и адрес2 будут иметь одинаковое значение (ссылка на объект Address), а изменение состояния адреса1 будет видно через address2 до тех пор, как ни address1 или address2 сами переназначен.)

Если это фактическое представление кода, это стоило бы делать некоторые исследования по mutable structs, так что вы по крайней мере, иметь полное представление о различных подводных камнях может столкнуться.

+0

Я понимаю причину: другими словами, если IPAddress публиковал свои поля, я мог бы воспроизвести тот же результат. – enzom83

+1

Если вы можете изменить состояние объекта, на которое ссылается строковая переменная (или поле), фактически не перезаписывая ссылку, вы можете наблюдать изменения в обеих структурах. Но вы не изменяли состояние (и вы не можете, по крайней мере, не в безопасном коде), вы полностью переписываете ссылку посредством переназначения. Во втором случае вы изменили состояние объекта, на который ссылались оба экземпляра struct. Значение IP, * ссылка на объект * не изменилось, поэтому вы наблюдали результаты в обоих. –

+1

Я не уверен, что ваше утверждение верно: «Это не имеет никакого отношения к тому факту, что строка является ссылочным типом или любыми специальными правилами или любым предложением неизменности». Если строка была обычным ссылочным типом, то изменение значения с помощью одной ссылки изменило бы ее для всех ссылок. Но строка - это неизменный ссылочный тип, что означает, что он ведет себя как тип значения. http://stackoverflow.com/questions/636932/in-c-why-is-string-a-reference-type-that-behaves-like-a-value-type. Ваше утверждение верно, если вы предполагаете, что «=» всегда означает назначение ссылки, но для кого-то это означает значение копирования. – BlueMonkMN

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

Вы выбираете страницу книги наугад и назовите ее «СПРАВОЧНИК ОДИН». На этой странице вы пишете «127.0.0.1». Вы берете кусок сырой бумаги и пишите «REFERENCE ONE, 8080» на этой бумаге и вставляете ее в ящик с надписью «адрес один».

MyIPEndPoint address2 = address1; 

Вы делаете фотокопию бумаги в «адрес один» и помещаете копию в «адрес два».

address2.IP.IpAsString = "255.255.255.255"; 

Вы открываете выдвижной ящик «адрес два» и видите, что он говорит «СПРАВОЧНИК ОДИН». Вы просматриваете книгу, пока не найдете страницу с надписью «REFERENCE ONE». Вы выцарапаете то, что там, и замените его новым текстом.

address2.Port = 9090; 

Вы открываете ящик «адрес два» и царапаете «8080» и заменяете его «9090». Вы оставляете СПРАВОЧНИК ОДИН, где он есть.

И теперь, когда вы закончите, ящик «адрес один» содержит «ССЫЛКА ОДИН, 8080», ящик «адрес два» содержит «СПРАВОЧНИК ОДИН, 9090», а в книге есть страница «REFERENCE ONE: 255.255 .255.255" .

Теперь вы понимаете разницу между ссылочными и стоимостными типами?

+0

Да, этот пример помог мне лучше понять разницу между ссылочными и стоимостными типами! – enzom83

Смежные вопросы