//
{
}
Заметьте, что внешний код не будет иметь непосредственного доступа к полю MyInt из-за его уровня доступа (оно описано как частное). Напротив, такой код должен использовать свойство для того, чтобы получить доступ к полю.
Функция set присваивает полю значение аналогичным образом. В этом случае можно воспользоваться ключевым словом value, чтобы сослаться на значение, полученное от пользователя.
Поле, используемое свойством private int myInt; | |||
Свойство | |||
public int MyIntProp | |||
get { return myInt; } | |||
set { myInt = value; } } |
//
//
{
Значение value имеет такой же тип, что и свойство, поэтому, если используется тот же тип, что и тип поля, не приходится беспокоиться о приведении типов.
Это простое свойство не только защищает поле myint от непосредственного доступа. Истинная мощь свойств проявляется, когда требуется несколько больший контроль над происходящим. Например, мы можем реализовать блок set следующим образом:
set {
if (value >= 0 && value <= 10)
myInt = value; }
В данном случае мы изменяем myInt, только если значение свойства находится между 0 и 10. В подобных ситуациях необходимо решить: что предпринять, если использовано недопустимое значение? Здесь имеются четыре возможности: - ничего не предпринимать (как в коде, приведенном выше);
- присвоить полю значение по умолчанию;
- продолжать как ни в чем ни бывало, но зарегистрировать событие для последующего анализа;
- сгенерировать исключительную ситуацию.
Вообще говоря, предпочтительными являются две последние возможности, а выбор между ними зависит от того, каким образом будет использоваться данный класс и в какой степени следует передать контроль пользователям класса. Генерирование исключительной ситуации дает пользователям очень большие возможности контроля, позволяет получать информацию о том, что происходит, и соответственно реагировать. Для этого можно воспользоваться стандартной исключительной ситуацией System, например
set {
if (value >= 0 && value <= 10) myInt = value; else
throw (new ArgumentOutOfRangeException("MyIntProp", value, "Значение свойства MylntProp должно лежать в диапа-
зоне между 0 и 10.")); }
Эту ситуацию можно обработать, применив конструкцию try. . .catch. . .finally в коде, который использует данное свойство, как это было описано в главе 7.
Регистрация данных, возможно, в текстовом файле, может оказаться полезной для производственных программ, в которых никаких проблем возникать не должно. Эти данные позволяют разработчикам проверять работоспособность программ и, при необходимости, исправлять обнаруженные ошибки.
Свойства, так же как и методы, могут использовать ключевые слова virtual, override и abstract, т. е. те ключевые слова, использование которых для полей оказывается недопустимым.
ПРАКТИКА: использование полей, методов и свойств |
1. Создайте новый проект консольного приложения.
2. С помощью VS добавьте новый класс с именем MyClass в файл MyClass.cs.
3. Измените код в MyClass.cs следующим образом:
public class MyClass { public readonly string Name; private int intVal; public intVal { get { return intVal; } set {
if (value >= 0 && value <= 10) intVal = value; else
throw (new ArgumentOutOfRangeException("Val" , value, "Val must be assigned a value between 0 and 10. ")) ; // Значение,
присваиваемое Val, должно лежать в диапазоне между 0 и 10. / '
} }
public override string ToStrmg() { return "Name: " + Name + "\nVal: " + Val; } private MyClass () : this("Default Name") { } public MyClass(string newName)
Name = newName; intVal = 0; } }
4. Измените код в Class1.cs следующим образом:
static void Main(string[] args) {
Console.WriteLine("Creating object myObj...");
MyClass myObj = new MyClass("My Object") ; Console.WriteLine("myObj created."); for (int i = -1; i <= 0; i++) {
try {
Console.WriteLine ("\nAttempting to assign {0} to myObj .Val. . . " , i) ; myObj.Val = i;
Console.WriteLine("Value {0} assigned to myObj.Val.", myObj.Val); } catch (Exception e) {
Console.WriteLine("Exception {0} thrown.", e.GetType().FuilName);
Console.WriteLine("Message:\n\"{0}\"", e.Message); ) )
Console.WriteLine("\nOutputting myObj .ToString() ..."); Console.WriteLine(myObj.ToString());
Console.WriteLine("myObj .ToString() Output.") ; } 5. Запустите приложение (см. рис. слева).
Как это работает
Код в Main () создает и использует экземпляр класса MyClass, который определен в MyClass.cs. Создание экземпляра данного класса должно осуществляться с помощью конструктора не по умолчанию, поскольку конструктор по умолчанию класса MyClass является частным:
private MyClass () : this(“Default Name") { }
Обратите внимание, что используется this ('Default Name') –для гарантированного присвоения Name какого-либо значения при обращении к конструктору. Последнее возможно, если данный класс используется для создания нового класса. Отсутствие значения у поля Name в дальнейшем может привести к возникновению ошибок.
Используемый не по умолчанию конструктор присваивает значения описанному как readonly полю паше (это присваивание можно осуществить либо при объявлении поля, либо с помощью конструктора) и частному полю intVal.
Далее Main() пытается выполнить два присваивания свойству val объекта myObj (он является экземпляром класса MyClass). Для присваивания значений -1 и 0 используются два прохода цикла for, а для обнаружения возможных исключительных ситуаций применяется конструкция try...catch. Когда свойству присваивается -1, возникает исключительная ситуация System.ArgumentOutOfRangeException и код, находящийся в блоке catch, выводит информацию о ней в окно консоли. При следующем проходе цикла свойству Val присваивается значение 0, а затем через это свойство значение присваивается частному полю intVal.
В заключение для вывода отформатированной строки, представляющей содержимое объекта, используется переопределенный метод ToString ():
public override string ToString() { | |
return "Name: " + Name + "\nVal:" + Val; } |
Этот метод должен быть объявлен с использованием ключевого слова override, поскольку названный метод переопределяет виртуальный метод ToString() базового класса System.Object. Код в данном случае использует непосредственно свойство val, а не частное поле intVal. Нет никаких причин, по которым мы не могли бы использовать свойства внутри класса подобным образом, хотя в этом случае может возникать небольшое замедление работы программы (настолько небольшое, что мы вряд ли сумеем его обнаружить). Использование свойства, кроме того, позволяет осуществлять контроль за допустимостью значений, присущий использованию свойств, что также может оказаться полезным для кода, находящегося внутри класса.
7. 2.Свойства членов
Последняя из основных тем, которую необходимо рассмотреть,– изменение свойств членов с помощью окна Properties. После того как мы выбрали член в окне Class View, мы получаем возможность просматривать его свойства в окне Properties (см рис слева).
Из этого окна мы можем непосредственно изменять многие свойства, например, уровень доступности, посредством свойства Access (доступ) (оно выделено на рисунке) В этом случае код будет модифицирован автоматически – и не придется ничего набирать на клавиатуре.
Обратите внимание, что свойство CanOverride (может переопределяться) позволяет определить, является ли член виртуальным, a IsShared (с разделением доступа) – является ли он статическим.
7.2.1.Дополнительные действия с членами класса
Теперь, когда вы познакомились с основами определения членов, самое время перейти к рассмотрению более сложных тем. Здесь будут рассмотрены следующие из них:
- сокрытие методов базового класса;
- вызов переопределенных или скрытых методов базового класса; - вложенные определения типов;
- сокрытие методов базового класса.
При наследовании некоторого (не абстрактного) члена базового класса наследуется также и его реализация. Если наследуемый член является виртуальным, то имеется возможность переопределить его реализацию, воспользовавшись ключевым словом override. Независимо от того, является ли наследуемый член виртуальным, при необходимости можно скрыть его реализацию. Это полезно, например, если наследуемый общий член работает не так, как требуется. Сокрытие достигается использованием следующего кода:
public class MyBaseClass { public void DoSomething ()
{
// Базовая реализация
} }
public class MyDerivedClass : MyBaseClass { public void DoSomething ()
{
// Реализация в производном классе, скрывающая базовую реализацию } }
Хотя такой код будет работать нормально, он сгенерирует предупреждение о том, что в нем скрывается член базового класса. Это дает возможность исправить положение дел, если мы случайно скрыли член, который на самом деле желаем использовать. Если же этот член действительно требуется скрыть, то об этом можно сказать явно, воспользовавшись ключевым словом new:
public class MyDerivedClass : MyBaseClass { new public void DoSomething()
{ // Реализация в производном классе, скрывающая базовую реализацию } }
Этот код будет работать точно так же, только без выдачи предупреждения.
На данном этапе нам представляется важным отметить, в чем заключается разница между скрытыми и переопределенными членами базового класса. Рассмотрим следующий код:
public class MyBaseClass {
public virtual void DoSomething()
{
Console.WriteLine("Базовая реализация") ;
} }
public class MyDerivedClass : MyBaseClass { public override void DoSomething()
(
Console.WriteLine("Производная реализация") ; } }
В данном случае переопределенный метод заменяет собой реализацию базового класса, так что в следующем коде будет использоваться новая версия, несмотря на то, что обращение к методу происходит через тип базового класса (с использованием полиморфизма)
MyDerivedClass myObj = new MyDenvedClass ();
MyBaseClass myBaseObj; myBaseObj = myObj; myBaseObj.DoSomething();
В данном случае выходной поток будет следующим:
В качестве альтернативы мы можем скрыть метод базового класса с помощью следующего кода: