Опубликован: 23.07.2006 | Доступ: свободный | Студентов: 2218 / 892 | Оценка: 4.28 / 4.17 | Длительность: 21:37:00
Специальности: Системный архитектор
Лекция 2:

Обзор языка C#

< Лекция 1 || Лекция 2: 123456 || Лекция 3 >

Индексаторы

Методы доступа в 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
< Лекция 1 || Лекция 2: 123456 || Лекция 3 >