Симон Робинсон - C# для профессионалов. Том II
Модуль класса Employee в VB
Следующий код представляет попытку закодировать модуль класса Employee на VB. Модуль класса предоставляет два открытых свойства: EmployeeName и Salary, а также открытый метод GetMonthlyPayment(), возвращающий сумму, которую компания должна платить сотруднику каждый месяц. Это не совпадает с зарплатой частично потому, что зарплата предполагается выплачиваемой за год, и частично потому, что позже будет представлена возможность прибавления других выплат компании сотруднику (таких, как бонусы за производительность):
' локальные переменные для хранения значений свойств
Private mStrEmployeeName As String ' локальная копия
Private mCurSalary As Currency ' локальная копия
Public Property Let Salary(ByVal curData As Currency)
mCurSalary = curData
End Property
Public Property Get Salary() As Currency
Salary = mCurSalary
End Property
Public Property Get EmployeeName() As String
EmployeeName = mStrEmployeeName
End Property
Public Sub Create(sEmployeeName As String, curSalary As Currency)
mStrEmployeeName = sEmployeeName
mCurSalary = curSalary
End Sub
Public Function GetMonthlyPayment() As Currency
GetMonthlyPayment = mCurSalary/12
End Function
В реальной жизни будет написано, по-видимому, что-то более сложное, но и этого класса будет достаточно для иллюстрации рассматриваемых нами концепций. Фактически мы уже имеем проблему с этим модулем класса VB — имена большинства людей меняются не очень часто, вот почему свойство EmployeeName предназначено только для чтения. Это по-прежнему оставляет необходимость задавать имя в первый раз. Для этого добавлен метод Create, который определяет имя и зарплату. Таким образом, процесс создания объекта сотрудника будет выглядеть так:
Dim Britney As Employee
Set Britney = New Employee
Britney.Create "Britney Spears", 20000
Эта схема работает, но она не очень удобна. Проблема с инициализацией объекта Employee состоит в том, что хотя VB предоставляет для этой цели методы Class_Load и Class_Initialize, метод Class_Load не может получать никаких параметров. Это означает, что нельзя выполнить никакой инициализации, которая является специфической для данного экземпляра Employee, поэтому необходимо просто написать отдельный метод инициализации Create и надеяться, что все, кто пишет клиентский код, никогда не будут забывать его вызывать. Такое решение неудобно, так как нет никакого смысла иметь объект Employee, у которого не заданы имя и зарплата, но именно это присутствует в приведенном выше коде в течение короткого периода между созданием экземпляра Britney и инициализацией объекта. Пока будут помнить о вызове метода Create, все будет нормально, но здесь имеется потенциальный источник ошибок.
В C# ситуация совершенно другая. Здесь в конструкторы можно подставлять параметры (эквивалент в C# для метода Class_Load). Необходимо только убедиться, что при определении класса Employee в C# конструктор получает Name и Salary в качестве параметров. В C# можно будет написать:
Employee Britney = new Employee("Britney Spears", 20000.00M);
что значительно изящнее и менее подвержено ошибкам. Отметим кстати символ "М", добавленный к зарплате. Это связано с тем, что эквивалент C# для типа Currency из VB называется десятичным значением и 'M', добавленный к числу в C#, подчеркивает, что число надо интерпретировать как decimal. Его указывать не обязательно, но это полезно для дополнительной проверки во время компиляции.
Класс Employee в C#
Помня о приведенных выше замечаниях можно теперь представить первое определение версии C# класса Employee (отметим, что здесь показано определение класса, а не определение содержащего его пространства имен):
class Employee {
private readonly string name;
private decimal salary;
public Employee(string name, decimal salary) {
this.name = name;
this.salary = salary;
}
public string Name {
get {
return name;
}
}
public virtual decimal Salary {
get {
return salary;
}
set {
salary = value;
}
}
public decimal GetMonthlyPayment() {
return salary/12;
}
public override string ToString() {
return "Name: " + name + ", Salary: $" + salary.ToString();
}
}
Просматривая этот код, мы видим сначала пару закрытых переменных — так называемых полей-членов, соответствующих переменным-членам в модуле класса VB. Поле name помечено как readonly. Мы скоро узнаем его точное значение. Грубо говоря, это гарантирует, что данное поле задано, когда создавался объект Employee, и не может впоследствии изменяться. В C# обычно не используют "венгерский" стиль именования объектов для имен переменных, поэтому они просто называются name и salary, а не mStrEmployeeName и mCurSalary. "Венгерский" стиль именования объектов означает, что имена переменных имеют префикс из букв, который указывает их тип (mStr, mCur и т.д.). Это на сегодня неважно, так как редакторы являются более развитыми и могут автоматически предоставить информацию о типах данных. Поэтому рекомендуется не использовать "венгерский" стиль именования объектов в программах C#.
В классе Employee существует также конструктор, пара свойств — Name и Salary, а также два метода — GetMonthlyPayment() и ToString(). Все это будет рассмотрено далее.
Отметим кстати, что имена свойств Name и Salary отличаются только регистром символов от имен своих соответствующих полей. Это не является проблемой, так как C# различает регистр символов. Способ, которым здесь именованы свойства и поля, соответствует обычному соглашению в C# и показывает, как можно на самом деле воспользоваться различием регистра символов.
Конструктор Employee
После объявления полей в приведенном выше коде располагается "метод", имя которого — Employee, совпадает с именем класса, то есть перед нами находится конструктор. Однако этот конструктор получает параметры и делает то же самое, что и метод Create в версии VB — он использует параметры для инициализации полей-членов:
public Employee(string name, decimal salary) {
this.name = name;
this.salary = salary;
}
Существует потенциальная синтаксическая проблема, так как явные имена параметров совпадают с именами полей — name и salary. Но она разрешается с помощью использования ссылки this, помечающей поля. Можно было бы вместо этого дать параметрам другие имена, но способ, которым это было сделано, является достаточно ясным и означает, что параметры сохраняют очевидные простые имена, которые соответствуют их значениям. Это обычный способ действий для C# в таких ситуациях.
Теперь можно объяснить точное значение квалификатора readonly перед именем поля:
private readonly string name;
Если поле помечено как readonly, то единственным местом, где ему может быть присвоено значение, является конструктор класса. Компилятор будет инициировать ошибку, если встретит код, который попытается изменить значение переменной readonly, в любом месте, кроме конструктора. Это предоставляет надежную гарантию, что переменная не будет изменена, если она была задана. Невозможно сделать что-либо подобное в VB, так как VB не имеет конструкторов, которые получают параметры, поэтому переменные уровня класса в VB должны быть инициализированы с помощью методов или свойств, вызываемых после создания экземпляра объекта.
Между прочим этот конструктор не просто позволяет задать параметры для инициализации объекта Employee — он заставляет это сделать. Если написать код следующего вида:
Employee Britney = new Employee; // неправильно
то он на самом деле не откомпилируется. Компилятор будет инициировать ошибку, так как в C# должен всегда вызываться конструктор, когда создается новый объект. Но никаких параметров задано не было, а единственный доступный конструктор требует двух параметров. Поэтому просто невозможно создать объект Employee без каких-либо параметров. Это страхует от ошибок, вызываемых неинициализированными объектами Employee.
Можно задать в классе более одного конструктора, чтобы выбрать, какое множество желательно использовать при создании нового объекта этого класса. Мы увидим, как это делается позже в данном приложении. Однако для этого конкретного класса единственного конструктора вполне достаточно.
Свойства класса Employee