Глава 10.
свойства
Эта глава посвящена свойствам. Свойства позволяют обратиться к методу в ис-
ходном тексте программы с использованием упрощенного синтаксиса. CLR под-
держивает два вида свойств: без параметров, их называют просто —
свойства
, и с
параметрами — у них в разных языках разное название. Например, в C# свойства
с параметрами называют
индексаторами
, а в Visual Basic —
свойствами по умолчанию
.
Кроме того, в этой главе рассказывается об инициализации свойств при помощи
инициализаторов объектов и коллекций, а также о механизме объединения свойств
посредством анонимных типов и типа
System.Tuple
.
свойства без параметров
Во многих типах определяется информация состояния, которую можно прочитать
или изменить. Часто эта информация состояния реализуется полями типа. Вот,
например, определение типа с двумя полями:
public sealed class Employee {
public String Name; // Имя сотрудника
public Int32 Age; // Возраст сотрудника
}
Создавая экземпляр этого типа, можно получить или задать любые сведения
о его состоянии при помощи примерно такого кода:
Employee e = new Employee();
e.Name = "Jeffrey Richter"; // Задаем имя сотрудника
e.Age = 48; // Задаем возраст сотрудника
Console.WriteLine(e.Name); // Выводим на экран "Jeffrey Richter"
Этот способ чтения и записи информации состояния объекта очень распро-
странен. Однако я считаю, что реализация такого вида совершенно недопустима.
Одним из краеугольных камней объектно-ориентированного программирования
и разработки является
инкапсуляция данных
. Инкапсуляция данных означает, что
поля типа ни в коем случае не следует открывать для общего доступа, так как в этом
случае слишком просто написать код, способный испортить сведения о состоянии
объекта путем ненадлежащего использования полей. Например, следующим кодом
разработчик может легко повредить объект
Employee
:
e.Age = -5; // Можете вообразить человека, которому минус 5 лет?
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
). Нельзя перегружать свойства
(то есть определять несколько свойств с одинаковыми именами, но разным типом).
266
Do'stlaringiz bilan baham: |