Структури з загальнодоступними зміненими полями або властивостями не є злими.
Методи структури (на відміну від установників властивостей), які мутують "це", є дещо злими, лише тому, що .net не забезпечує способів відрізнити їх від методів, яких немає. Методи структури, які не мутують "це", повинні бути використані навіть на структурах, які мають лише читання, без необхідності захисного копіювання. Методи, які мутують "це", взагалі не повинні використовуватися в структурах лише для читання. Оскільки .net не хоче забороняти методам структури, які не змінюють "це", не викликати в структурах лише для читання, але не хоче дозволити мутувати структури лише для читання, він захисно копіює структури в " лише контексти, мабуть, отримуючи найгірше з обох світів.
Незважаючи на проблеми з поводженням з методами самовиключення в контекстах, доступних лише для читання, змінні структури часто пропонують семантику, що набагато перевершує типи класів, що змінюються. Розглянемо наступні три методи підпису:
struct PointyStruct {public int x, y, z;};
клас PointyClass {public int x, y, z;};
недійсний метод1 (Foot PointyStruct);
недійсний метод2 (ref PointyyStruct foo);
недійсний метод3 (PointyClass foo);
Для кожного методу дайте відповідь на наступні питання:
- Якщо припустити, що метод не використовує жодного "небезпечного" коду, він може змінити foo?
- Якщо до виклику методу не існує зовнішніх посилань на 'foo', чи може після цього існувати зовнішня посилання?
Відповіді:
Запитання 1::
Method1()
ні (чіткий намір)
Method2()
: так (чіткий намір)
Method3()
: так (невизначений намір)
Питання 2
Method1()
:: ні
Method2()
: ні (якщо не небезпечно)
Method3()
: так
Метод1 не може змінювати foo і ніколи не отримує посилання. Метод2 отримує короткочасне посилання на foo, який може використовувати модифікувати поля foo будь-яку кількість разів у будь-якому порядку, поки він не повернеться, але він не може зберегти це посилання. Перед тим, як Method2 повернеться, якщо він не використовує небезпечний код, будь-яка та всі копії, які могли бути зроблені з його "foo", зникнуть. Method3, на відміну від Method2, отримує незрозумілу посилання на foo, і немає жодної інформації про те, що це може зробити з цим. Він може взагалі не змінювати foo, він може змінювати foo, а потім повертатися, або він може давати посилання на foo на інший потік, який може мутувати його якось довільним чином у якийсь довільний майбутній час.
Масиви структур пропонують чудову семантику. З огляду на RectArray [500] типу Rectangle, зрозуміло і очевидно, як, наприклад, скопіювати елемент 123 в елемент 456, а потім через деякий час встановити ширину елемента 123 на 555, не порушуючи елемент 456. "RectArray [432] = RectArray [321 ]; ...; RectArray [123]. Ширина = 555; ". Знаючи, що Прямокутник - це структура з цілим полем під назвою Ширина, скаже все, що потрібно знати про вищезазначені твердження.
Тепер припустимо, що RectClass був класом з тими ж полями, що і Rectangle, і один хотів виконувати ті самі операції на RectClassArray [500] типу RectClass. Можливо, масив повинен містити 500 попередньо ініціалізованих незмінних посилань на змінні об'єкти RectClass. у цьому випадку правильний код буде чимось на зразок "RectClassArray [321] .SetBounds (RectClassArray [456]); ...; RectClassArray [321] .X = 555;". Можливо, у масиві передбачається наявність примірників, які не збираються змінювати, тому власний код буде більше схожий на "RectClassArray [321] = RectClassArray [456]; ...; RectClassArray [321] = Новий RectClass (RectClassArray [321 ]); RectClassArray [321] .X = 555; " Щоб знати, що потрібно робити, треба було б знати набагато більше і про RectClass (наприклад, чи підтримує він конструктор копій, метод копіювання з тощо. ) та призначене використання масиву. Ніде не так чисто, як використання структури.
Для переконання, на жаль, немає жодного приємного способу для будь-якого класу контейнерів, крім масиву, запропонувати чисту семантику структури масиву. Найкраще, що можна зробити, якби хотілося, щоб колекція індексувалася, наприклад, рядком, ймовірно, було б запропонувати загальний метод "ActOnItem", який би прийняв рядок для індексу, загального параметра та делегата, який буде переданий шляхом посилання як на загальний параметр, так і на предмет колекції. Це дозволило б отримати майже ту саму семантику, що і масиви stru, але якщо тільки vb.net та C # люди не можуть переслідувати запропонувати хороший синтаксис, код буде чітко виглядає, навіть якщо він має досить ефективні дії (передаючи загальний параметр дозволяють використовувати статичний делегат і уникають необхідності створювати будь-які екземпляри тимчасового класу).
Особисто я підглядаю ненависть до Еріка Ліпперта та ін. spew щодо змінних типів значень. Вони пропонують набагато більш чисту семантику, ніж розбещені типи посилань, які використовуються всюди. Незважаючи на деякі обмеження з підтримкою .net для типів значень, є багато випадків, коли типи змінних значень краще підходять, ніж будь-який інший тип сутності.
int
s,bool
s, а всі інші типи цінностей є злими. Існують випадки незмінність та незмінність. Ці випадки залежать від ролі, яку відіграють дані, а не типу розподілу / обміну пам'яттю.