Опубликован: 19.02.2009 | Доступ: свободный | Студентов: 3054 / 785 | Оценка: 4.35 / 4.11 | Длительность: 16:28:00
ISBN: 978-5-94774-401-9
Лекция 13:

Классы

Операции класса

С# позволяет переопределить большинство операций так, чтобы при использовании их объектами конкретного класса выполнялись действия, отличные от стандартных. Это дает возможность применять объекты собственных типов данных в составе выражений, например:

newObject x, y, z;
...
z = x+y; // используется операция сложения, переопределенная для класса newObject

Определение собственных операций класса называют перегрузкой операций. Перегрузка операций обычно применяется для классов, для которых семантика операций делает программу более понятной. Если назначение операции интуитивно непонятно, перегружать такую операцию не рекомендуется.

Операции класса описываются с помощью методов специального вида, синтаксис которых выглядит следующим образом:

[ атрибуты] спецификаторы объявитель_операции
{тело}

В качестве спецификаторов одновременно используются ключевые слова public и static. Кроме того, операцию можно объявить как внешнюю - extern. Объявление операции может выглядеть по-разному, в зависимости от того, что мы перегружаем: унарную или бинарную операцию.

При описании операций необходимо соблюдать следующие правила:

  1. операция должна быть описана как открытый статический метод класса ( public static );
  2. параметры в операцию должны передаваться по значению (то есть недопустимо использовать параметры ref и out );
  3. сигнатуры всех операций класса должны различаться;
  4. типы, используемые в операции, должны иметь не меньшие права доступа, чем сама операция (то есть должны быть доступны при использовании операции).

Унарные операции

В классе можно переопределять следующие унарные операции: + - ! ~ ++ --, а также константы true и false. При этом, если была перегружена константа true, то должна быть перегружена и константа false, и наоборот.

Синтаксис объявителя унарной операции:

тип operator унарная_операция (параметр)

Примеры заголовков унарных операций:

public static int operator + (DemoArray m)
public static DemoArray operator --(DemoArray m)
public static bool operator true (DemoArray m)

Параметр, передаваемый в операцию, должен иметь тип класса, для которого она определяется. При этом операция должна возвращать:

  1. для операций +, -, !, ~ величину любого типа;
  2. для операций ++, -- величину типа класса, для которого она определяется;
  3. для операций true и false величину типа bool.

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

В качестве примера рассмотрим класс DemoArray, реализующий одномерный массив, в котором содержатся следующие функциональные элементы:

  1. конструктор, позволяющий создать объект-массив заданной размерности;
  2. конструктор, позволяющий инициализировать объект-массив обычным массивом;
  3. свойство, возвращающее размерность массива;
  4. индексатор, позволяющий просматривать и устанавливать значение по индексу в закрытом поле-массиве;
  5. метод вывода закрытого поля-массива;
  6. перегрузка операции унарный минус (все элементы массива меняют свое значение на противоположное);
  7. перегрузка операции инкремента (все элементы массива увеличивают свое значение на 1);
  8. перегруза констант true и false (при обращении к объекту будет возвращаться значение true, если все элементы массива положительные, в противном случае, будет возвращаться значение false).
class DemoArray
 {
  int[] MyArray;//закрытый массив
  
  public DemoArray(int size)//конструктор 1
  {
   MyArray = new int[size];
  }

  public DemoArray(params int[] arr)//конструктор 2
  {
   MyArray = new int[arr.Length];
   for (int i = 0; i < MyArray.Length; i++) MyArray[i] = arr[i];
  }
    
  public int LengthArray //свойство, возвращающее размерность
  {
   get { return MyArray.Length; }
  }

  public int this[int i] //индексатор
  {
   get
   {
    if (i < 0 || i >= MyArray.Length) throw new Exception("выход за границы массива");
    return MyArray[i];
   }
   set
   {
    if (i < 0 || i >= MyArray.Length) throw new Exception("выход за границы массива");
    else MyArray[i] = value;
   }
  }
  
  public static DemoArray operator -(DemoArray x) //перегрузка операции унарный минус
  {
   DemoArray temp = new DemoArray(x.LengthArray);
   for (int i = 0; i < x.LengthArray; ++i)
    temp[i] = -x[i];
   return temp;
  } 

  public static DemoArray operator ++(DemoArray x) //перегрузка операции инкремента
  {
   DemoArray temp = new DemoArray(x.LengthArray);
   for (int i = 0; i < x.LengthArray; ++i)
    temp[i] = x[i]+1;  
   return temp;
  }

  public static bool operator true(DemoArray a) //перегрузка константы true
  {
   foreach (int i in a.MyArray)
   {
    if (i<0)
    {
     return false;
    }
   }
   return true;
  }

  public static bool operator false(DemoArray a)//перегрузка константы false
  {
   foreach (int i in a.MyArray)
   {
    if (i>0)
    {
     return true;
    }
   }
   return false;
  }

  public void Print(string name) //метод - выводит поле-массив на экран
  {
   Console.WriteLine(name + ": ");
   for (int i = 0; i < MyArray.Length; i++)
    Console.Write(MyArray[i] + " ");
   Console.WriteLine();
  }
 }

 class Program
 {
  static void Main()
  {
   try
   {
    DemoArray Mas = new DemoArray(1, -4, 3, -5, 0); //вызов конструктора 2
    Mas.Print("Исходный массив");
    Console.WriteLine("\nУнарный минус");
    DemoArray newMas=-Mas; //применение операции унарного минуса 
    Mas.Print("Mассив Mas"); //обратите внимание, что создается новый объект и знаки меняются
    newMas.Print("Массив newMas"); //только у нового массива 
    Console.WriteLine("\nОперация префиксного инкремента");
    DemoArray Mas1=++Mas;
 Mas.Print("Mассив Mas"); 
    Mas1.Print("Mассив Mas1=++Mas");
    Console.WriteLine("\nОперация постфиксного инкремента");
    DemoArray Mas2=Mas++;
    Mas.Print("Mассив Mas"); 
    Mas2.Print("Mассив Mas2=Mas++");
    if (Mas)
     Console.WriteLine("\nВ массиве все элементы положительные\n");
    else Console.WriteLine("\nВ массиве есть не положительные элементы\n");
   }
   catch (Exception e)
   {
    Console.WriteLine(e.Message);
   }
 }
Задание. Добавьте в класс DemoArray переопределение унарного плюса (все элементы массива преобразуются в положительные), унарного декремента (все элементы массива уменьшаются на единицу)

Бинарные операции

При разработке класса можно перегрузить следующие бинарные операции: + - * / % & | ^ << >> == != < > <= >=. Обратите внимание, операций присваивания в этом списке нет.

Синтаксис объявителя бинарной операции:

тип operator бинарная_операция (параметр1, параметр 2)

Примеры заголовков бинарных операций:

public static DemoArray operator + (DemoArray a, DemoArray b)
public static bool operator == (DemoArray a, DemoArray b)

При переопределении бинарных операций нужно учитывать следующие правила:

  1. Хотя бы один параметр, передаваемый в операцию, должен иметь тип класса, для которого она определяется.
  2. Операция может возвращать величину любого типа.
  3. Операции отношений определяются только парами и обычно возвращают логическое значение. Чаще всего переопределяются операции сравнения на равенство и неравенство для того, чтобы обеспечить сравнение значения некоторых полей объектов, а не ссылок на объект. Для того чтобы переопределить операции отношений, требуется знание стандартных интерфейсов, которые будут рассматриваться чуть позже.

В качестве примера вернемся к классу DemoArray, реализующему одномерный массив, и добавим в него две версии переопределенной операции +:

  • Вариант 1: добавляет к каждому элементу массива заданное число;
  • Вариант 2: поэлементно складывает два массива
class DemoArray
 {
  ...
  public static DemoArray operator +(DemoArray x, int a) //вариант 1
  {
   DemoArray temp = new DemoArray(x.LengthArray);
   for (int i = 0; i < x.LengthArray; ++i)
    temp[i]=x[i]+a;
   return temp;
  }

  public static DemoArray operator +(DemoArray x, DemoArray y) //вариант 2
  {
   if (x.LengthArray == y.LengthArray)
   {
    DemoArray temp = new DemoArray(x.LengthArray);
    for (int i = 0; i < x.LengthArray; ++i)
     temp[i] = x[i] + y[i];
    return temp;
   }
   else throw new Exception("несоответствие размерностей"); 
  }
 }

 class Program
 {
  static void Main()
  {
   try
   {
    DemoArray a = new DemoArray(1, -4, 3, -5, 0);
    a.Print("Массива a");
    DemoArray b=a+10;
    b.Print("\nМассива b");
    DemoArray c = a+b;
    c.Print("\nМассива c");
   }
   catch (Exception e)
   {
    Console.WriteLine(e.Message);
   }
  }
 }
Задание. Добавьте в класс DemoArray переопределение бинарного минуса (из всех элементов массива вычитается заданное число) и операции & (поэлементно сравнивает два массива, если соответствующие элементы попарно совпадают, то операция возвращает значение true, иначе false ).

Операции преобразования типов

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

explicit operator целевой_тип (параметр)  //явное преобразование
implicit operator целевой_тип (параметр)  //неявное преобразование

Эти операции выполняют преобразование из типа параметра в тип, указанный в заголовке операции. Одним из этих типов должен быть класс, для которого выполняется преобразование.

Неявное преобразование выполняется автоматически в следующих ситуациях:

  1. при присваивании объекта переменной целевого типа;
  2. при использовании объекта в выражении, содержащем переменные целевого типа;
  3. при передаче объекта в метод параметра целевого типа;
  4. при явном приведении типа.

Явное преобразование выполняется при использовании операции приведения типа.

При определении операции преобразования типа следует учитывать следующие особенности:

  1. тип возвращаемого значения (целевой_тип) включается в сигнатуру объявителя операции;
  2. ключевые слова explicit и implicit не включаются в сигнатуру объявителя операции.

Следовательно, для одного и того класса нельзя определить одновременно и явную, и неявную версию. Однако, т.к. неявное преобразование автоматически выполнятся при явном использовании операции приведения типа, то достаточно разработать только неявную версию операции преобразования типа.

В качестве примера вернемся к классу DemoArray, реализующему одномерный массив, и добавим в него неявную версию переопределения типа DemoArray в тип одномерный массив и наоборот:

class DemoArray
 {
  …

  public static implicit operator DemoArray (int []a) //неявное преобразование типа int [] в DemoArray
  {
   return new DemoArray(a); 
  }

  public static implicit operator int [](DemoArray a) //неявное преобразование типа DemoArray в int []
  {
   int []temp=new int[a.LengthArray];
   for (int i = 0; i < a.LengthArray; ++i)
    temp[i] = a[i];
   return temp; 
  }
 }

 class Program
 {
  static void arrayPrint(string name, int[]a) //метод, который позволяет вывести на экран одномерный массив
  {
   Console.WriteLine(name + ": ");
   for (int i = 0; i < a.Length; i++)
    Console.Write(a[i] + " ");
   Console.WriteLine();
  }

  static void Main()
  {
   try
   {
    DemoArray a = new DemoArray(1, -4, 3, -5, 0);
    int []mas1=a; //неявное преобразование типа DemoArray в int []
    int []mas2=(int []) a; //явное преобразование типа DemoArray в int []
    DemoArray b1 =mas1; //неявное преобразование типа int [] в DemoArray    
    DemoArray b2 =(DemoArray)mas2; //явное преобразование типа int [] в DemoArray
    
    //изменение значений
    mas1[0]=0;  mas2[0]=-1;   
    b1[0]=100;  b2[0]=-100;

    //вывод на экран
    a.Print("Массива a"); 
    arrayPrint("Maccив mas1", mas1);
    arrayPrint("Maccив mas2", mas2);
    b1.Print("Массива b1");
    b2.Print("Массива b2");
    }
   catch (Exception e)
   {
    Console.WriteLine(e.Message);
   }
  }
 }
Задание. В методе Main используется операция приведения типа DemoArray к int[] (и наоборот) в явном виде, хотя явная версия операции преобразования типа DemoArray к int[] не была определена. Объясните, почему возможно использование явного приведения типа.