Найважливішою відмінністю між a class
і a struct
є те, що відбувається в наступній ситуації:
Річ річ1 = нова річ ();
thing1.somePropertyOrField = 5;
Річ річ2 = річ1;
thing2.somePropertyOrField = 9;
На що має вплинути остання заява thing1.somePropertyOrField
? Якщо Thing
структура, і somePropertyOrField
є відкритим публічним полем, об'єкти thing1
і thing2
будуть "відриватися" один від одного, тому останнє твердження не вплине thing1
. Якщо Thing
це клас, то thing1
і thing2
вони будуть приєднані один до одного, і тому остання заява буде писати thing1.somePropertyOrField
. Слід використовувати структуру у випадках, коли колишня семантика мала б більше сенсу, а клас слід використовувати у випадках, коли друга семантика мала б більше сенсу.
Зауважте, що хоча деякі люди радять, що бажання зробити щось змінне - це аргумент на користь того, щоб це був клас, я б припустив, що це зворотне: якщо щось, що існує для того, щоб зберігати деякі дані, стане змінним, і якщо не буде очевидно, чи приєднані екземпляри до чого-небудь, річ повинна бути структурою (швидше за все, з відкритими полями), щоб зрозуміти, що екземпляри не прикріплені ні до чого іншого.
Наприклад, розглянемо твердження:
Особа somePerson = myPeople.GetPerson ("123-45-6789");
somePerson.Name = "Мері Джонсон"; // Була "Мері Сміт"
Чи змінить друге твердження інформацію, що зберігається myPeople
? Якщо Person
структура відкритого поля, вона не буде, і той факт, що вона не буде, буде очевидним наслідком того, що вона буде структурою відкритого поля ; якщо Person
це структура та бажає оновити myPeople
, явно потрібно було б зробити щось на кшталт myPeople.UpdatePerson("123-45-6789", somePerson)
. Якщо Person
це клас, однак, може бути набагато складніше визначити, чи не буде вищезазначений код ніколи не оновлювати вміст MyPeople
, завжди оновлювати його, а іноді й оновлювати.
Щодо уявлення про те, що структури повинні бути "непорушними", я взагалі не згоден. Існують дійсні випадки використання для "незмінних" структур (де інваріанти примусово виконуються в конструкторі), але вимагають, щоб вся структура повинна бути переписана будь-коли, коли будь-яка її зміна буде незручною, марною і більш схильною, щоб викликати помилки, ніж просто викриття поля безпосередньо. Наприклад, розглянемо PhoneNumber
структуру, поля якої включають серед інших, AreaCode
і Exchange
, припустимо, одна має List<PhoneNumber>
. Ефект наступного повинен бути досить чітким:
for (int i = 0; i <myList.Count; i ++)
{
PhoneNumber theNumber = myList [i];
if (theNumber.AreaCode == "312")
{
рядок newExchange = "";
якщо (new312to708Exchanges.TryGetValue (theNumber.Exchange), вийдіть newExchange)
{
theNumber.AreaCode = "708";
theNumber.Exchange = newExchange;
myList [i] = число;
}
}
}
Зауважте, що ніщо у наведеному вище коді не знає і не піклується про будь-які поля, PhoneNumber
крім AreaCode
та Exchange
. Якби PhoneNumber
так звана "незмінна" структура, потрібно було б або забезпечити withXX
метод для кожного поля, який би повертав новий екземпляр структури, який утримував передане значення у вказаному полі, або ж це було б необхідно для коду, як описано вище, щоб знати про кожне поле структури. Не зовсім привабливий.
До речі, є щонайменше два випадки, коли структури можуть обгрунтовано містити посилання на типи змінних.
- Семантика структури вказує на те, що вона зберігає ідентичність відповідного об'єкта, а не як ярлик для зберігання властивостей об'єкта. Наприклад, "KeyValuePair" міститиме ідентичність певних кнопок, але, як очікується, він не буде зберігати стійку інформацію про положення цих кнопок, виділяти стани тощо.
- Структура знає, що вона містить єдине посилання на цей об’єкт, ніхто більше не отримає посилання, і будь-які мутації, які коли-небудь будуть виконуватись на цей об’єкт, будуть зроблені до того, як посилання на нього зберігається в будь-якому місці.
У колишньому сценарії Ідентифікатор об'єкта буде незмінним; по-друге, клас вкладеного об'єкта може не забезпечити незмінність, але структура, що містить посилання, буде.