Свойства
Свойства расширяют возможности полей данных. Они представляют собой типизированные объекты, у которых можно получить значение и записать в них новое значение. При выполнении этих действий выполняется определяемый программистом код. Синтаксис определения полей следующий:
<модификаторы> < тип> < имя>{ get { /* Тело get */ } set { /* Тело set */ }}
В теле get пишется код, который выполняется при получении значения свойства. В теле set пишется код, который выполняется при установке нового значения. При этом в set доступно присваеваемое значение через ключевое слово value:
public class SampleClass
{
private static int a;
public static int A
{
get { return a; }
set { a = value; }
}
public static void Main() { A = 2; Console.WriteLine("A = {0}", A); }
}
Этот пример демонстрирует распространенный прием инкапсуляции полей.
Свойство – это пара методов со специальными именами. Метод set() вызывается при изменении значения свойства, метод get() – при получении значения свойства. Обращение к свойству выглядит как обращение к полю данных, но транслируется в вызов одного из двух методов. Имя метода обычно близко к имени поля (например, если имя переменной - name, то свойству дают имя - Name).
|
|
Каждый из методов свойства: set() и get() может иметь свой собственный модификатор доступа. При работе со свойствами часто нужно решить, какой модификатор доступа следует использовать для того, чтобы реализовать нужную стратегию доступа к полю класса. Существует пять наиболее употребительных стратегий:
* чтение и запись (Read, Write);
* чтение и запись только при первом обращении (Read, Write-once);
* только чтение (Read-only);
* только запись (Write-only);
* ни чтения, ни записи (Not Read, Not Write).
Открытость свойств (атрибут public) позволяет реализовать только первую стратегию.
class PropertyClass1
{
private int x;
public int X
{
set { x=value; }
get { return x; }
}
}
Определяя в свойстве только один из двух методов, получаем свойства только для чтения или только для записи.
// Стратегия только чтения или только записи
class PropertyClass1
{
private int x, y;
public int X { get { return x; } }
public int Y { set { y=value; } }
}
Однако, возможен и другой вариант реализации той же стратегии, использующий манипуляцию уровнями доступа к методам, реализующим свойство:
// Та же стратегия - только чтения или только записи
class PropertyClass1
{
private int x, y;
public int X
{
private set { x=value; }
get { return x; }
}
public int Y
{
set { y=value; }
private get { return x; }
}
}
Одним из важных направлений развития языков программирования является удобство и наглядность кода. Именно стремление упростить структуру кода привело в свое время к перегрузке операторов в С++ и свойств в Object Pascal. Предлагая новый язык специально для своей новой платформы, Microsoft, разумеется, не могла не учесть это сторону вопроса. Поэтому язык включает в себя свойства (properties) и индексаторы (indexers).
|
|
При написании приложений часто возникает необходимость создавать объекты, представляющие собой коллекцию других объектов (они не обязательно могут явно хранить в себе все эти объекты, а только предоставлять доступ к ним). В языках вроде Cи или Java для этого использовались специальные функции (методы) вроде GetItem(int index). Конечно, это возможный подход, но часто, когда нужно часто обращаться к элементам коллекции, неудобно каждый раз писать такие громоздкие конструкции.
Свойство, обеспечивающее доступ к массиву, называется индексатор. Синтаксически объявление индексатора - такое же, как и в случае свойств, но методы get и set приобретают аргументы по числу размерности массива, задающего индексы элемента, значение которого читается или обновляется. Важным ограничением является то, что у класса может быть только индексатор со стандартным именем this.
Индексаторы предоставляют синтаксис сходный с тем, который используется при адресации массива, за исключением того, что в качестве типа данных индекса, можно использовать любой тип. Для того, чтобы объявить в классе индексатор, нужно использовать следующий синтаксис:
<тип> this [список_параметров]
где тип – это тип возвращаемого значения (тип элемента коллекции), а список параметров – это типы и имена параметров, используемых для идентификации элемента коллекции. Индексатор может иметь любые модификаторы, кроме static, а к его параметрам нельзя применять модификаторы out и ref.
Для определения кода индексатора, нужно прописать ему метода доступа - get и, возможно, set (если индексатор у вас позволяет менять элементы). При этом в методе get доступны все параметры индексатора как переменные, а в методе set дополнительно к ним параметр value, представляющий новое значение элемента. Таким образом, индексатор примет вид, аналогичный следующему:
public object this [int index]
{
get { return GetItem(index); }
set { SetItem(index, value); }
}
Для полной ясности картины, приведем небольшой пример:
using System;
public CustomIndexerClass
{
// Объявляем внутренний массив, где будут храниться действительные значения
private int internalArray = new int[10];
// Объявляем индексатор
public int this [int index]
{
get // Доступ на чтение
{
// Проверка границ
if (index < internalArray.GetLowerBound(0) ||
index > internalArray.GetUpperBound(0))
return 0;
// Вoзвращаем значение соответствующего элемента массива
return internalArray[index];
}
set
{
// Проверка границ и запись нового значения
if (index >= internalArray.GetLowerBound(0) &&
index <= internalArray.GetUpperBound(0))
internalArray[index] = value;
}
}
public class ApplicationClass
{
public static void Main()
{
CustomIndexerClass myIndexer = new CustomIndexerClass();
myIndexer[1] = 10;
myIndexer[5] = 7;
for (int i = 0; i < 10; i++)
Console.WriteLine("myIndexer[{0}] = {1}\n", i, myIndexer[i]);
}
}
}
Эта программа создает класс, имеющий индексатор, который просто читает или записывает значения во внутреннем массиве. Главная программа сначала изменяет несколько ячеек массива, пользуясь индексатором, а затем читает все его ячейки последовательно.
Статические классы
В версию 2.0 введена возможность описывать статический класс, то есть класс с модификатором static. Экземпляры такого класса создавать запрещено, и кроме того, от него запрещено наследовать. Все элементы такого класса должны явным образом объявляться с модификатором static (константы и вложенные типы классифицируются как статические элементы автоматически). Конструктор экземпляра для статического класса задавать, естественно, запрещается.
Для каждого объекта при его создании в памяти выделяется отдельная область, в которой хранятся его данные. Кроме того, в классе могут присутствовать статические элементы, которые существуют в единственном экземпляре для всех объектов класса. Часто статические данные называют данными класса, а остальные - данными экземпляра. Функциональные элементы класса не тиражируются, то есть всегда хранятся в единственном экземпляре для всех экземпляров класса. Для работы с данными класса используются методы класса (статические методы), для работы с данными экземпляра — методы экземпляра, или просто методы.
|
|
Частичные классы
С выходом.Net 2.0 в С# появилась новая синтаксическая конструкция – частичные классы. Обычный класс должен быть целиком описан в одном файле, а описание частичного класса может быть разбито по нескольким файлам. Программистам практически не нужна эта возможность – никому не приходит в голову разбивать описание по нескольким файлам. Однако это очень удобно, когда программист использует какое-нибудь инструментальное средство для генерации части кода. Например, дизайнер Windows Forms автоматически создает код, описывающий содержимое формы. В Visual Studio 2003 он записывал этот код в тот же самый файл, в котором программист описывал логику работы приложения. В Visual Studio 2005 этот код пишется уже в отдельный файл, и вероятность того, что программист случайно повредит его, становится минимальной.
По аналогии можно предположить, что частичные методы тоже можно описывать в двух файлах – половину в одном, а половину в другом. Но на самом деле это не так. Частичные методы устроены совершенно иначе, чем частичные классы. Частичные методы могут использоваться только внутри частичных классов. Отсюда они вероятно и получили свое название. Один из файлов, в которых описывается класс, должен содержать описание метода – название, тип возвращаемого значения и список параметров, но без тела метода, например, вот так:
partial class PartialMethodSample
{
partial void PrintMessage(string msg);
public void Connect()
{
PrintMessage("Connecting");
// Здесь должен быть код, выполняющий соединение с базой данных
PrintMessage("Connected");
}
}
Ну и зачем нам нужно ключевое слово "partial"? – спросите вы. Все это можно было бы сделать и раньше, да к тому же не нужно было заранее объявлять метод в первом файле! Все дело в том, что у частичного метода тела может и не быть. Даже если я целиком уберу второй файл, проект все равно будет отлично компилироваться. Но как же так? Получается, что мы вызываем метод, у которого нет тела! Но это же бессмысленно!
|
|
Дело в том, что если компилятор обнаруживает вызов частичного метода, для которого отсутствует тело, а есть только описание сигнатуры, он просто пропускает этот вызов и не компилирует его. Так, как если бы вызова этого метода вообще бы не было. Такое поведение кажется странным, но давайте вспомним, зачем были придуманы частичные классы. Для удобства автоматической генерации кода. И частичные методы нужны для того же самого. Предположим, что первый файл сгенерировал какой-нибудь мастер или визуальный редактор. Он не знает, нужно ли выводить сообщения, а если и нужно, то куда: в консоль, в базу данных или каждое из них нужно показать отдельным окошком. Тем не менее, наш мастер создает частичный метод, использует его в своем коде, но тела не определяет. Предполагается, что программист, использующий этот автоматически сгенерированный класс, просто создаст дополнительный файл, в котором укажет, куда и как выводить сообщения. В нашем примере – на консоль.
Таким образом:
- Разделение классов (partial types) - это возможность разделять код класса на несколько частей.
- Эта возможность очень полезна, если
• класс очень велик
• над одним классам параллельно должны работать несколько программистов
• часть класса генерируется некоторым генератором исходного кода (например, дизайнером форм/компонентов), а часть пишется вручную программистом
Пример:
public class User
{
private int mInt;
private long mLong;
private string mString;
private bool mBool;
public int MyInt
{
get{ return this.mInt; }
set{ this.mInt = value; }
}
}
Пример разделенного класса:
public partial class User
{
private int mInt;
private long mLong;
private string mString;
private bool mBool;
}
public partial class User
{
public int MyInt
{
get{return this.mInt;}
set{this.mInt = value;}
}
}
Резюме:
- Эти два способа равнозначны.
- Части класса могут находиться в одном или разных файлах (но вряд ли этому может найтись применение)
Использование возможности разделения классов предоставляет следующие возможности:
- Разделять функции класса по нескольким файлам, что позволит одновременно работать с разными частями класса нескольким разработчикам.
- Отделить код создаваемый генератором кода и разработчиком, как например это сделано для поддержки дизайнера форм в Visual Studio 2005. Там код располагающий элементы на форме по умолчанию скрыт и отделен от код написанного программистом с использованием ключевого слова partial.