Индексаторы

Свойства

Свойства расширяют возможности полей данных. Они представляют собой типизированные объекты, у которых можно получить значение и записать в них новое значение. При выполнении этих действий выполняется определяемый программистом код. Синтаксис определения полей следующий:

<модификаторы> < тип> < имя>{ 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.

Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: