264
Глава.10 .Свойства
Есть и другие причины для инкапсуляции доступа к полям данных типа. До-
пустим, вам нужен доступ к полю, чтобы что-то сделать, разместить в кэше неко-
торое значение или создать какой-то внутренний объект, создание которого было
отложено, причем обращение к полю не должно нарушать безопасность потоков.
Или, скажем, поле является логическим и его значение представлено не байтами
в памяти, а вычисляется по некоторому алгоритму.
Каждая из этих причин заставляет при
разработке типов, во-первых, помечать
все поля как закрытые (
private
), во-вторых, давать пользователю вашего типа
возможность получения и задания сведений о состоянии через специальные мето-
ды, предназначенные исключительно для этого. Методы, выполняющие функции
оболочки для доступа к полю, обычно называют
методами доступа
(accessor).
Методы доступа могут выполнять дополнительные проверки, гарантируя, что
сведения о состоянии объекта никогда не будут искажены. Я переписал класс из
предыдущего примера следующим образом:
public sealed class Employee {
private String m_Name; // Поле стало закрытым
private Int32 m_Age; // Поле стало закрытым
public String GetName() {
return(m_Name);
}
public void SetName(String value) {
m_Name = value;
}
public Int32 GetAge() {
return(m_Age);
}
public void SetAge(Int32 value) {
if (value < 0)
throw new ArgumentOutOfRangeException("value", value.ToString(),
"The value must be greater than or equal to 0");
m_Age = value;
}
}
Несмотря на всю простоту, этот пример демонстрирует огромное преимущество
инкапсуляции полей данных. Он также показывает, как просто создаются свойства,
доступные только для чтения или только для записи — достаточно опустить один
из методов доступа. В качестве альтернативы можно позволить изменять значения
только в производных типах —
для этого метод
SetXxx
помечается как защищенный
(
protected
).
Как видите, у инкапсуляции данных есть два недостатка: во-первых, из-за реали-
зации дополнительных методов приходится писать более длинный код, во-вторых,
вместо простой ссылки на имя поля пользователям типа приходится вызывать
соответствующие методы:
265
Свойства.без.параметров
e.SetName("Jeffrey Richter"); // Обновление имени сотрудника
String EmployeeName = e.GetName(); // Получение возраста сотрудника
e.SetAge(41); // Обновление возраста сотрудника
e.SetAge(-5); // Выдача исключения
// ArgumentOutOfRangeException
Int32 EmployeeAge = e.GetAge(); // Получение возраста сотрудника
Лично я считаю эти недостатки незначительными.
Тем не менее CLR поддержи-
вает механизм свойств, частично компенсирующий первый недостаток и полностью
устраняющий второй.
Следующий класс функционально идентичен предыдущему, но в нем исполь-
зуются свойства:
public sealed class Employee {
private String m_Name;
private Int32 m_Age;
public String Name {
get { return(m_Name); }
set { m_Name = value; } // Ключевое слово value
} // идентифицирует новое значение
public Int32 Age {
get { return(m_Age); }
set {
if (value < 0) // Ключевое слово value всегда
// идентифицирует новое значение
throw new ArgumentOutOfRangeException("value", value.ToString(),
"The value must be greater than or equal to 0");
m_Age = value;
}
}
}
Как видите, хотя свойства немного усложняют определение типа, дополнитель-
ная работа более чем оправдана, потому что она позволяет писать код следующего
вида:
e.Name = "Jeffrey Richter"; // "Задать" имя сотрудника
String EmployeeName = e.Name; // "Получить" имя сотрудника
e.Age = 41; // "Задать" возраст сотрудника
e.Age = -5; //
Вброс исключения
// ArgumentOutOfRangeException
Int32 EmployeeAge = e.Age; // "Получить" возраст сотрудника
Можно считать свойства «умными» полями, то есть полями с дополнительной
логикой. CLR поддерживает статические, экземплярные, абстрактные и вирту-
альные свойства. Кроме того, свойства могут помечаться модификатором доступа
(см. главу 6) и определяться в интерфейсах (см. главу 13).
У каждого свойства есть имя и тип (но не
void
).
Нельзя перегружать свойства
(то есть определять несколько свойств с одинаковыми именами, но разным типом).