Операции. Перегрузка операций

  • 24.04.2019

Как известно, в языке С# тип переменной определяет набор значений, которые она может хранить, а также набор операций, которые можно выполнять над этой переменной. Например, над значением переменной типа int программа может выполнять сложение, вычитание, умножение и деление. С другой стороны, использование оператора “плюс” для сложения двух экземпляров реализованного программистом класса лишено смысла.

Когда в программе определяется класс, то по существу определяется новый тип данных. Тогда язык C# позволяет определить операции, соответствующие этому новому типу данных.

Перегрузка операций состоит в изменении смысла операции при использовании его с определенным классом.

Например, пусть имеется:

myclass a,bc;…//a,b,c-экземпляры класса myclass

c=a+b; //перегруженная операция сложения для класса myclass

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

Общий синтаксис объявления перегруженной операции:

[атрибуты] спецификаторы operator тело операции,

Спецификаторы – public,static,extern

operator – ключевое слово, определяющее перегруженную операцию

тело операции-действия, которые выполняются при использовании операции в выражении

Перегружать можно только стандартные операции.

Алгоритм перегрузки операции :

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

    Для перегрузки операций используется ключевое слово operator .

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

Правила перегрузки операции :

    Операция должна быть объявлена как public static

    Параметры в операцию должны передаваться по значению (не ref, не out)

    Двух одинаковых перегруженных операций в классе не должно быть

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

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

К унарным операциям, которые можно перегружать в языке С# относятся:

    унарные + и –

    логическое!,

    true, false – обычно перегружаются для типов SQL

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

public static тип_возвр_знач operator унарная_операция (один параметр),

где параметр – это класс, для которого перегружается данная операция

Например,

public static myclass operator ++(myclass x)

public static int operator +(myclass x)

public static bool operator true(myclass x)

Перегруженная операция возвращает:

    унарные + и –, ! величину любого типа

    Величину типа класса

    true, false – величину типа bool

Префиксные и постфиксные ++ и – не различаются при перегрузке.

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

Одномерный массив

public MyArray(int size)

a=new int;

public MyArray(params int mas)

length =mas.length;

a=new int;

for (int i=0;i

public static MyArray operator ++(MyArray x) // перегрузка унарного оператора ++

MyArray temp=new MyArray(x.length);

for (int i=0;i

temp[i]=++x.a[i]; //попробуйте temp.a[i]=++x.a[i]

//индексатор, в случае выхода за рамки массива – генерируется исключение!

public int this

get {if (i>=0 && i

set { if (i>=0 && i

public void Print(string name)

Console.WriteLine(name+”:”);

for (int i=0;i

Console.WriteLine(“\t”+a[i]);

Console.WriteLine();

public void Enter()

//в цикле - ввод элементов массива – реализуйте сами!

//данные класса – сам массив и его размерность

MyArray a1=new MyArray(5,2,-1,1,-2);

a1.Print(“Первый массив ”);

a 1++; //теперь к экземпляру класса можно применить операцию ++

a1.Print(“Использование операции ++ для всех элементов массива ”);

MyArray a2=new MyArray(5);

a2.Print(“Второй массив ”);

a 2++;

a2.Print(“Использование операции ++ для всех элементов массива”);

catch (Exception e)

{Console.WriteLine(e.Message);}

Перекрытие методов.

Пример перекрытия метода в C#:

Public class myClass { // виртуальный метод public virtual string ToString() { return ""; } } public class myDelivierClass: myClass { // переопределенный, перекрытый метод public override string ToString() { return "new return"; } }

Сокрытие методов

Даже если в базовом классе метод не был объявлен виртуальным, в производном классе все равно можно объявить другой метод с такой же сигнатурой. Новый метод однако не перекроет метод базового класса. Напротив, говорят, что он скроет метод базового класса. При этом компилятор, решая, какой метод вызвать, всегда будет рассматривать тип данных, на который указывает переменная, как тип данных заданный при ее объявлении!

Скрытие (переопределение) элементов класса – это явное описание в классе-наследнике с новыми характеристиками уже существующих элементов из наследуемого класса.

Public class MyClass { public int MyNumber; } public class OurClass: MyClass { public double MyNumber; }

Переопределено поле MyNumber (в родительском классе имело целочисельный тип, а в производном – дробный). Поле целого типа скрыто новым описанием. Если бы поле MyNumber не было переопределено, то в классе OurClass (производный) по умолчанию было бы доступен только родительское поле MyNumber типа int. А выражение: oc.MyNumber = 5.5; вызывало ошибку типов.

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

Если метод скрывает базового класса, то к его определению необходимо добавить ключевое слово new. Этот модификатор подскажет компилятору, что программист знает о факте сокрытия!

Public class Customer { public string GetFunnyString() { return "Cusomer funny!"; } } ... public class Nevermore60Customer: Customer { public new string GetFunnyString() { return "Nevermore60 funny!”; } } ... Customer Cust1; Nevermore60Customer Cust2; Cust1 = new Customer(); Console.WriteLine(Cust1.GetFunnyString()); Cust1 = new Nevermore60Customer(); Console.WriteLine(Cust1.GetFunnyString()); Cust2 = new Nevermore60Customer(); Console.WriteLine(Cust2.GetFunnyString()); ... } Результат: Cusomer funny! Cusomer funny! Nevermore60 funny!

В большинстве случаев лучше перекрыть (override ), а не скрыть методы, так как сокрытие методов представляет собой серьезную опасность того, что для данного экземпляра класса будет вызван «неправильный» метод.

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

Если хотя бы один метод в классе абстрактный, то и весь класс должен быть абстрактным! Кроме того, любой класс, порожденный от абстрактного должен перекрыть его методы.

Public abstract class Customer { public abstract int MyAbstractMethod(); // тело отсуствует... public class Nevermore60Customer: Customer { public override int MyAbstractMethod() { return 0; } } }

Запечатанные (sealed) классы и методы можно рассматривать как противоположность абстрактным классам и методам. Объявление класса или метода запечатанным означает что произвести наследование и перекрытие не возможно.

Sealed class FinalClass // наследовать не возможно... public class myClass { // перекрытие в дальнейшем не озможно public sealed override FinalMethod() } ...

Вызов базовых версий методов

Ключевое слово base явно указывает компилятору, что происходит обращение к методу базового класса.

Public class CustomerAccount { public virtual decimal CalculatePrice (CustomerAccount account) { // ... return 2000M; } } public class GoldAccount: CustomerAccount { public override decimal CalculatePrice(CustomerAccount account) { return base.CalculatePrice(account) * 0.9M; } }

Индексаторы, операции, свойства можно делать виртуальными и перекрывать их по желанию.

Поля не могут быть объявлены виртуальными или перекрытыми. Однако можно скрыть базовую версию поля, объявив другое поле с тем же именем в производном классе с модификатором (new). Получить доступ к базовому полю можно через base.[имя_поля].

Статические методы не могут быть объявлены виртуальными, но могут быть скрытыми (new). Не имеет смысла объявлять виртуальным статический член вообще!

Перегрузка методов

Перегрузка методов в C# означает, что в классе можно определить несколько методов с одним и тем же именем при условии, что эти методы получают разное число параметров. Не следует путать перегрузку методов с перекрытием методов. Перегрузка методов не имеет ничего общего с наследованием или виртуальными методами.

C# не разрешает использование значений по умолчанию для параметров.

Пример перегрузки методов: Console.WriteLine();

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

String s1 = "Иванов" ; string s2 = " Сергей" ; string s3 = s1 + s2; // s3 теперь содержит "Иванов Сергей"

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

Терминологическое примечание . Часто в русскоязычных текстах смешивают понятия «операция» и «оператор». Оператор — это наименьшая автономная часть языка программирования, команда или набор команд (составной оператор). Опера́ция - конструкция языка, аналогичная по записи математическим операциям, то есть специальный способ записи некоторых действий. Путаница связана еще и с тем, что в С и C++ присваивание и инкремент/декремент являются и операторами, и операциями. Чаще всего, операция является частью оператора. Поэтому далее будем употреблять термин «перегрузка операции».

Язык Си шарп предоставляет возможность строить специальные классы, которые также уникально реагируют на один и тот же набор базовых лексем (вроде операции +). Необходимо знать, что абсолютно каждую встроенную операцию C# перегружать нельзя. В таблице описаны возможности перегрузки основных операций:

Операция C# Возможность перегрузки
+, -, !, ++, —, true, false Этот набор унарных операций может быть перегружен
+, -, *, /, %, &, |, ^, <<, >> Эти бинарные операции могут быть перегружены
==, !=, <, >, <=, >= Эти операции сравнения могут быть перегружены. C# требует совместной перегрузки «подобных» операций (т.е. < и >, <= и >=, == и!=)
Операция не может быть перегружена. Аналогичную функциональность предлагают индексаторы
() Операция () не может быть перегружена, но ту же функциональность предоставляют специальные методы преобразования
+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>= Сокращенные операции присваивания не могут перегружаться; однако вы получаете их автоматически, перегружая соответствующую бинарную операцию

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

// Форма перегрузки унарной операции public static возвращаемый_тип operator op (тип_параметра операнд) { // операторы } // Форма перегрузки бинарной операции public static возвращаемый_тип operator op (тип_параметра1 операнд1, тип_параметра2 операнд2) { // операторы }

Вместо op подставляется перегружаемая операция, например + или / , а возвращаемый_тип обозначает конкретный тип значения, возвращаемого указанной операцией. Это значение может быть любого типа, но зачастую оно указывается такого же типа, как и у класса, для которого перегружается операция. Такая корреляция упрощает применение перегружаемых операций в выражениях. Для унарных операций операнд обозначает передаваемый операнд, а для бинарных операций то же самое обозначают операнд1 и операнд2 . Заметим, что операторные методы должны иметь оба спецификатора типа — public и static.

Пример перегрузки бинарной операции:

Using System; namespace ПерегрузкаОпераций { class Vector { // Координаты точки в трехмерном пространстве public int x, y, z; // конструктор public Vector(int x = 0, int y = 0, int z = 0) { this.x = x; this.y = y; this.z = z; } // Перегружаем бинарную операцию + (сложение векторов) public static Vector operator + (Vector v1, Vector v2) { Vector v = new Vector(); v.x = v1.x + v2.x; v.y = v1.y + v2.y; v.z = v1.z + v2.z; return v; } // Перегружаем бинарную операцию - (разность векторов) public static Vector operator - (Vector v1, Vector v2) { Vector v = new Vector(); v.x = v1.x - v2.x; v.y = v1.y - v2.y; v.z = v1.z - v2.z; return v; } public void printV(string s) { Console.WriteLine(s + x + " " + y + " " + z); } } class Program { static void Main(string args) { Vector V1 = new Vector(3, 4, 5); Vector V2 = new Vector(-3, -4, 5); V1.printV("Координаты первого вектора: "); V2.printV("Координаты второго вектора: "); Vector V3 = V1 + V2; // ПЕРЕГРУЗКА! V3.printV("Координаты суммы векторов: "); V3 = V1 - V2; // ПЕРЕГРУЗКА! V3.printV("Координаты разности векторов: "); Console.ReadLine(); } } }

Результат:Здесь выполняются операции сложения и разности двух векторов (выделено красным цветом).

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

Основы перегрузки операторов

В C#, подобно любому языку программирования, имеется готовый набор лексем, используемых для выполнения базовых операций над встроенными типами. Например, известно, что операция + может применяться к двум целым, чтобы дать их сумму:

// Операция + с целыми. int а = 100; int b = 240; int с = а + b; //с теперь равно 340

Здесь нет ничего нового, но задумывались ли вы когда-нибудь о том, что одна и та же операция + может применяться к большинству встроенных типов данных C#? Например, рассмотрим такой код:

// Операция + со строками. string si = "Hello"; string s2 = " world!"; string s3 = si + s2; // s3 теперь содержит "Hello world!"

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

Язык C# предоставляет возможность строить специальные классы и структуры, которые также уникально реагируют на один и тот же набор базовых лексем (вроде операции +). Имейте в виду, что абсолютно каждую встроенную операцию C# перегружать нельзя. В следующей таблице описаны возможности перегрузки основных операций:

Операция C# Возможность перегрузки
+, -, !, ++, --, true, false Этот набор унарных операций может быть перегружен
+, -, *, /, %, &, |, ^, > Эти бинарные операции могут быть перегружены
==, !=, <, >, <=, >= Эти операции сравнения могут быть перегружены. C# требует совместной перегрузки "подобных" операций (т.е. < и >, <= и >=, == и!=)
Операция не может быть перегружена. Oднако, аналогичную функциональность предлагают индексаторы
() Операция () не может быть перегружена. Однако ту же функциональность предоставляют специальные методы преобразования
+=, -=, *=, /=, %=, &=, |=, ^=, >= Сокращенные операции присваивания не могут перегружаться; однако вы получаете их автоматически, перегружая соответствующую бинарную операцию

Перегрузка операторов тесно связана с перегрузкой методов. Для перегрузки оператора служит ключевое слово operator , определяющее операторный метод, который, в свою очередь, определяет действие оператора относительно своего класса. Существуют две формы операторных методов (operator): одна - для унарных операторов, другая - для бинарных. Ниже приведена общая форма для каждой разновидности этих методов:

// Общая форма перегрузки унарного оператора. public static возвращаемый_тип operator op(тип_параметра операнд) { // операции } // Общая форма перегрузки бинарного оператора. public static возвращаемый_тип operator op(тип_параметра1 операнд1, тип_параметра2 операнд2) { // операции }

Здесь вместо op подставляется перегружаемый оператор, например + или /, а возвращаемый_тип обозначает конкретный тип значения, возвращаемого указанной операцией. Это значение может быть любого типа, но зачастую оно указывается такого же типа, как и у класса, для которого перегружается оператор. Такая корреляция упрощает применение перегружаемых операторов в выражениях. Для унарных операторов операнд обозначает передаваемый операнд, а для бинарных операторов то же самое обозначают операнд1 и операнд2 . Обратите внимание на то, что операторные методы должны иметь оба спецификатора типа - public и static.

Перегрузка бинарных операторов

Давайте рассмотрим применение перегрузки бинарных операторов на простейшем примере:

Using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 { class MyArr { // Координаты точки в трехмерном пространстве public int x, y, z; public MyArr(int x = 0, int y = 0, int z = 0) { this.x = x; this.y = y; this.z = z; } // Перегружаем бинарный оператор + public static MyArr operator +(MyArr obj1, MyArr obj2) { MyArr arr = new MyArr(); arr.x = obj1.x + obj2.x; arr.y = obj1.y + obj2.y; arr.z = obj1.z + obj2.z; return arr; } // Перегружаем бинарный оператор - public static MyArr operator -(MyArr obj1, MyArr obj2) { MyArr arr = new MyArr(); arr.x = obj1.x - obj2.x; arr.y = obj1.y - obj2.y; arr.z = obj1.z - obj2.z; return arr; } } class Program { static void Main(string args) { MyArr Point1 = new MyArr(1, 12, -4); MyArr Point2 = new MyArr(0, -3, 18); Console.WriteLine("Координаты первой точки: " + Point1.x + " " + Point1.y + " " + Point1.z); Console.WriteLine("Координаты второй точки: " + Point2.x + " " + Point2.y + " " + Point2.z + "\n"); MyArr Point3 = Point1 + Point2; Console.WriteLine("\nPoint1 + Point2 = " + Point3.x + " " + Point3.y + " " + Point3.z); Point3 = Point1 - Point2; Console.WriteLine("\nPoint1 - Point2 = " + Point3.x + " " + Point3.y + " " + Point3.z); Console.ReadLine(); } } }

Перегрузка унарных операторов

Унарные операторы перегружаются таким же образом, как и бинарные. Главное отличие заключается, конечно, в том, что у них имеется лишь один операнд. Давайте модернизируем предыдущий пример, дополнив перегрузки операций ++, --, -:

Using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 { class MyArr { // Координаты точки в трехмерном пространстве public int x, y, z; public MyArr(int x = 0, int y = 0, int z = 0) { this.x = x; this.y = y; this.z = z; } // Перегружаем бинарный оператор + public static MyArr operator +(MyArr obj1, MyArr obj2) { MyArr arr = new MyArr(); arr.x = obj1.x + obj2.x; arr.y = obj1.y + obj2.y; arr.z = obj1.z + obj2.z; return arr; } // Перегружаем бинарный оператор - public static MyArr operator -(MyArr obj1, MyArr obj2) { MyArr arr = new MyArr(); arr.x = obj1.x - obj2.x; arr.y = obj1.y - obj2.y; arr.z = obj1.z - obj2.z; return arr; } // Перегружаем унарный оператор - public static MyArr operator -(MyArr obj1) { MyArr arr = new MyArr(); arr.x = -obj1.x; arr.y = -obj1.y; arr.z = -obj1.z; return arr; } // Перегружаем унарный оператор ++ public static MyArr operator ++(MyArr obj1) { obj1.x += 1; obj1.y += 1; obj1.z +=1; return obj1; } // Перегружаем унарный оператор -- public static MyArr operator --(MyArr obj1) { obj1.x -= 1; obj1.y -= 1; obj1.z -= 1; return obj1; } } class Program { static void Main(string args) { MyArr Point1 = new MyArr(1, 12, -4); MyArr Point2 = new MyArr(0, -3, 18); Console.WriteLine("Координаты первой точки: " + Point1.x + " " + Point1.y + " " + Point1.z); Console.WriteLine("Координаты второй точки: " + Point2.x + " " + Point2.y + " " + Point2.z + "\n"); MyArr Point3 = Point1 + Point2; Console.WriteLine("\nPoint1 + Point2 = " + Point3.x + " " + Point3.y + " " + Point3.z); Point3 = Point1 - Point2; Console.WriteLine("Point1 - Point2 = " + Point3.x + " " + Point3.y + " " + Point3.z); Point3 = -Point1; Console.WriteLine("-Point1 = " + Point3.x + " " + Point3.y + " " + Point3.z); Point2++; Console.WriteLine("Point2++ = " + Point2.x + " " + Point2.y + " " + Point2.z); Point2--; Console.WriteLine("Point2-- = " + Point2.x + " " + Point2.y + " " + Point2.z); Console.ReadLine(); } } }