Обзор языка C#
Индексаторы
Методы доступа в C# доступны не только для простых переменных, но и для элементов массивов. Пример описания индексатора показан на слайде. Из этого описания видно, что индексатор не может иметь произвольного имени; в данном примере мы использовали ключевое слово this как обозначение интерфейса, заданного по умолчанию. Если в классе реализовано несколько интерфейсов, то можно ввести и дополнительные индексаторы, обозначая их как InterfaceName.this . Наконец, заметим, что один и тот же массив можно индексировать с помощью переменных различных типов (например, используя int как ключ или string как имя для поиска в базе данных).
В качестве примера приведем класс, реализующий работу с квадратными матрицами. Понятно, что нам было бы привычнее иметь прямой доступ к элементам, т.е. писать просто A[i,j] , а не A.elements[i,j] . Реализуется это следующим образом:
public class Matrix { public const int n = 10; public int[,] elements = new int[n,n]; public int this[int i, int j] { get { return elements[i,j]; } set { elements[i,j] = value; } } }
При таком описании допустимо следующее использование:
Matrix a = new Matrix(); a[0,0] = 1; a[1,5] = 5; Matrix b = new Matrix(); b[0,0] = -4; b[1,5] = 10;
События
- Событийно-ориентированный подход типичен для современных приложений (работа в сети, GUI-интерфейсы и т.д.)
- В C# используется модель "публикация и подписка":
public delegate void EventHandler (string strText); ... evsrc.EventHandler += new EventHandler(CatchEvent);
Многие современные приложения требуют применения событийно-ориентированного подхода, например, это необходимо при создании распределенной системы, обменивающейся сообщениями, или при написании программ, использующих GUI-интерфейс. В C# используется модель "публикация/подписка" - класс публикует те события, которые он может инициировать, а другие классы могут подписаться на получение извещений о них. Для реализации этой модели используются представители (delegates) , которые выполняют в C# роль "безопасных указателей на функцию".
В следующем примере, иллюстрирующем использование представителей в C#, необходимо обратить внимание на фрагмент, в котором последовательно происходят подписка на событие, обработка события с помощью зарегистрированного обработчика и отказ от дальнейшей обработки события (см. оператор evsrc.TextOut -= EventHandler )
using System; public delegate void EventHandler (string strText); class EventSource { public event EventHandler TextOut; public void TriggerEvent() { if (TextOut != null) TextOut("Event triggered... "); } } class TestApp { public static void Main() { EventSource evsrc = new EventSource(); evsrc.TextOut += new EventHandler(CatchEvent); evsrc.TriggerEvent(); evsrc.TextOut -= new EventHandler(CatchEvent); evsrc.TriggerEvent(); TestApp theApp = new TestApp(); evsrc.TextOut += new EventHandler(theApp.InstanceCatch); evsrc.TriggerEvent(); } public static void CatchEvent(string strText) { WriteLine(strText); } public void InstanceCatch(string strText) { WriteLine("Instance "+strText); }
Перегрузка операторов
- Перегрузка - один из наиболее спорных механизмов современных языков
- Перегрузка используется для сокращения записи операций над объектами
public static Matrix operator+ (Matrix left, Matrix right) { ... } ... Matrix c = new Matrix(); c = a + b;
Перегрузка операторов - это один из наиболее спорных механизмов в современных языках. Некоторые программисты считают, что перегрузка операторов помогает только в создании трудно находимых ошибок. Другие программисты считают механизм перегрузки операторов полезным как раз из соображений улучшения читаемости кода. Как бы то ни было, перегрузка операторов стала неотъемлемой частью C#. Например, большинство классов в C# по умолчанию перегружают оператор сравнения (операция == практически всегда означает вызов метода Equals , унаследованного от System.Object ).
Перегрузка операторов обычно используется для того, чтобы сократить и привести к привычному виду запись операций над объектами, определенными программистом. Скажем, с объектами, представляющими математические или физические величины, обычно ассоциируются арифметические операции. Возьмем, в качестве примера квадратные матрицы - для них естественно ввести операции сложения и умножения. Без перегрузки операторов эти действия пришлось бы записывать таким образом: A.Add(B) или Matrices.Add (A,B) . И та, и другая формы записи несколько непривычны, так как традиционной формой является запись вида A+B . Попробуем реализовать ее на C#:
public class Matrix { ... public static Matrix operator+ (Matrix left, Matrix right) { Matrix target = new Matrix(); for (int i=0; i<n; i++) for (int j=0; j<n; j++) target[i,j] = left[i,j] + right[i,j]; return target; }
При таком описании запись сложения матриц приобретает более знакомый вид:
Matrix c = new Matrix(); c = a + b;
Таким же образом можно было бы реализовать умножение и деление матриц, сравнение и другие операции.
Явные и неявные преобразования
- Приведения бывают явными и неявными
- Классы и структуры могут задавать применимые к ним явные и неявные приведения:
public struct Rational { public static implicit operator Rational(int i) { return new Rational(i,1); } public static explicit operator double (Rational r) { return (double) r.Numerator / r.Denominator }
Во всех языках допустимы присваивания, в которых участвуют переменные похожих, но все-таки различных типов. Например в C# допустимы следующие операторы:
short v1 = 44; int v2 = v1;
Здесь во время второго присваивания выполняется неявное приведение переменной v1 к типу int . Однако неявные приведения возможны только при присваиваниях, в которых не может произойти потери данных, т.е. конечный тип должен содержать в себе все значения исходного типа1Отметим одно исключение: при преобразовании целой переменной в вещественный тип может произойти потеря точности, но с сохранением порядка . Обратное преобразование должно сопровождаться явным приведением :
v1 = (short) v2;
Применим эту идею для организации сокращенной записи преобразований. Предположим, что у нас есть класс Rational , реализующий рациональные числа. Опишем набор приведений для этого класса (обратите внимание, что Rational является структурой, а не классом - структуры предоставляют практически все те же языковые возможности, что и классы):
public struct Rational { public int Numerator, Denominator; ... public static implicit operator Rational(int i) { return new Rational(i,1); } public static explicit operator double (Rational r) { return (double) r.Numerator / r.Denominator } } Rational r = 4; // implicit conversion Rational r = new Rational(2,3); double d = (double)r; // error without explicit conversion