Учебное пособие (1077022), страница 11
Текст из файла (страница 11)
Далее следуют метод приведения кстроке ToString и методы, соответствующие перегруженным операторам.Вбольшинствеперегруженныхоператоровдляобозначенияпараметров методов применяют следующие сокращения: lhs – left hand side(левый оператор), rhs – right hand side (правый оператор).Как и в языке С++, здесь для перегрузки операторов используетсяключевое слово operator. В частности объявление метода:public static Number operator ++(Number lhs)означает, что данный метод соответствует реализации перегруженногооператора ++.
Перегрузка операторов в языке C# может быть реализованатолько в виде статического метода, который принимает параметр типаNumber и возвращает значение типа Number.Интересно то, что компилятор автоматически различает префикснуюи постфиксную форму оператора ++. В языке С++ для того, чтобыразличалисьпрефикснаяипостфикснаяперегрузки,используетсяспециальный фиктивный параметр типа int. Аналогично реализуетсяперегрузка оператора --.В случае перегрузки оператора, содержащего левую и правую части вметод передается два параметра:public static Number operator + (Number lhs, Number rhs)Методыперегрузкиоператоровподдерживаютполиморфизм.Например, можно перегрузить оператор +, который в качестве второгопараметра будет принимать тип не Number, а int:public static Number operator + (Number lhs, int rhs)91Перегрузка операторов в языке C# предполагает, что возвращаемоезначение будет новым объектом, например при перегрузке оператора +новый объект создается прямо в операторе return:return new Number(lhs.Num + rhs);Контрпример реализован при перегрузке оператора *.
В этом случаерезультат сохраняется в левый операнд, который возвращается из метода.Это – плохая практика, так как если левый операнд параметр ссылочноготипа, то получается, что оператор неявно меняет один из операндов.Поэтому при хорошей практике перегрузки операторов предполагается,что возвращаемое значение будет новым объектом, а параметры операторане изменяются.Операторы сравнения принимают два операнда, и в этом смысле ихперегрузка ничем не отличается от рассмотренной в приведенных вышепримерах. Однако их необходимо перегружать попарно: операторы == (равно) и != (не равно); операторы > (больше) и < (меньше); операторы => (больше или равно) и <= (меньше или равно).Если один из этих операторов перегружен, а другой нет, токомпиляторвыдает"Operators.Number.operatorошибку,например:«Для<(Operators.Number,оператораOperators.Number)"требуется, чтобы был определен соответствующий оператор ">"».Если перегружены операторы равенства и неравенства, то необходимотакже переопределить виртуальные методы Equals и GetHashCode.
Еслиони не переопределены, то компилятор выдает предупреждения: "Operators.Number" определяет оператор == или оператор !=,но не переопределяет Object.GetHashCode(). "Operators.Number" определяет оператор == или оператор !=,но не переопределяет Object.Equals(object o).92Операторыприведениятиповпереопределяютсянескольконеобычным способом.Пример оператора приведения к типу int:public static explicit operator int(Number lhs)Здесь вместо оператора указан тип данных int, и к нему будетосуществляться приведение типа.Перед ключевым словом operator указывается ключевое слово explicitили implicit. Слово explicit означает, что данный метод должен явноприменяться при использовании оператора приведения, а implicit – чтоданный метод может использоваться неявно при вычислении выражений.Особенным случаем перегрузки операторов является перегрузкаоператора «квадратные скобки».
В языке C# для такой перегрузкииспользуется отдельная конструкция языка, называющаяся индексатором.Индексатор напоминает свойство (property), однако у данного свойствапредусмотрены параметры, которые указываются в квадратных скобках.Пример объявления индексатора:public int this[int i]{get{return this.Num + i;}set{this.Num = value + i;}}В этом случае вместо имени свойства указывается ключевое словоthis, а после него в квадратных скобках даются параметры.
Параметровможет быть произвольное количество, причем их использование никак неограничено.Однакобольшинствопрограммистовинтуитивновоспринимают квадратные скобки как оператор для доступа к массиву иликоллекции. Поэтому в качестве параметра обычно передают какой-либо93ключ в числовой или символьной форме или набор ключей в случае многомерного массива.Почему в языке C# не используется оператор перегрузки квадратныхскобок в обычной форме? Потому что форма индексатора позволяетперегрузить сразу два оператора чтения и записи, что является наиболеепривычным для программиста поведением при перегрузке квадратныхскобок.Рассмотрим пример вызова перегруженных операторов:usingusingusingusingusingSystem;System.Collections.Generic;System.Linq;System.Text;System.Threading.Tasks;namespace Operators{class Program{static void Main(string[] args){Number a = new Number(1);Console.WriteLine("\nУнарный оператор");a++;Console.WriteLine("a={0}", a);++a;Console.WriteLine("a={0}", a);Console.WriteLine("\nБинарный оператор");Number b = new Number(100);Number c = a + b;Console.WriteLine("c={0}", c);int i = 333;Number d = a + i;Console.WriteLine("d={0}", d);//Ошибка, так как не перегружен оператор (int + Number)//Number d1 = i + d;Console.WriteLine("\nАналог \"внутренней перегрузки\"94оператора *");Number mul1 = new Number(5);Number mul2 = mul1 * 4;Console.WriteLine("mul1={0}", mul1);Console.WriteLine("mul2={0}", mul2);Console.WriteLine("\nПерегрузка операторов сравнения");Number eq1 = new Number(3);Number eq2 = new Number(2);Console.WriteLine("{0} == {1} ---> {2}",eq1, eq2, (eq1 == eq2));Console.WriteLine("{0} != {1} ---> {2}",eq1, eq2, (eq1 != eq2));Console.WriteLine("{0} > {1} ---> {2}",eq1, eq2, (eq1 > eq2));Console.WriteLine("{0} < {1} ---> {2}",eq1, eq2, (eq1 < eq2));Console.WriteLine("{0} >= {1} ---> {2}",eq1, eq2, (eq1 >= eq2));Console.WriteLine("{0} <= {1} ---> {2}",eq1, eq2, (eq1 <= eq2));Console.WriteLine("\nОператоры приведения типов");Console.WriteLine("Явное приведение типов");int intEq1 = (int)eq1;Console.WriteLine("{0} ---> {1}", eq1, intEq1);Console.WriteLine("Неявное приведение типов");double doubleEq1 = eq1;Console.WriteLine("{0} ---> {1}", eq1, doubleEq1);Console.WriteLine("\nИндексатор");Number index = new Number(1);index[330] = 3;Console.WriteLine("index[{0}] = {1}", 0, index[0]);Console.ReadLine();}}}Результат вывода в консоль:Вызов конструктора для 1Унарный операторВызов конструктора для 2a=295Вызов конструктора для 3a=3Бинарный операторВызов конструктора для 100Вызов конструктора для 103c=103Вызов конструктора для 336d=336Аналог "внутренней перегрузки" оператора *Вызов конструктора для 5mul1=20mul2=20Перегрузка операторов сравненияВызов конструктора для 3Вызов конструктора для 23 == 2 ---> False3 != 2 ---> True3 > 2 ---> True3 < 2 ---> False3 >= 2 ---> True3 <= 2 ---> FalseОператоры приведения типовЯвное приведение типов3 ---> 3Неявное приведение типов3 ---> 3ИндексаторВызов конструктора для 1index[0] = 33396Ниже будет рассмотрен пример разреженной матрицы, которыйиспользует индексатор.5.3 ОбобщенияОбобщения (generics) являются аналогом механизма языка С++который называется шаблонами (templates).Как и шаблоны, обобщения позволяют одинаковым способомпроизводить одинаковые действия для различных типов данных.Однако реализация обобщений в .NET несколько отличается отреализации шаблонов в компиляторе языка С++.
Если программа на языкеC# содержит обобщенный класс, то он будет скомпилирован в MSIL собобщенным типом. Подстановка реальных типов вместо обобщенноготипа будет произведена уже на этапе JIT-компиляции. Таким образом,работу с обобщениями поддерживает не только компилятор, но и самасреда .NET runtime.В языке Java существует механизм, аналогичный тому, что и в языкеC#, который также называется обобщениями.Рассмотрим работу с обобщениями на фрагментах примера 7.Пример обобщенного класса:/// <summary>/// Обобщенный класс/// </summary>/// <typeparam name="T">Обобщенный тип</typeparam>class GenericClass1<T>{private T i;/// <summary>/// Конструктор/// </summary>public GenericClass1(){this.i = default(T);}/// <summary>97/// Метод инициализации значения/// </summary>public void SetValue(T param){this.i = param;}/// <summary>/// Приведение к строке/// </summary>public override string ToString(){return i.ToString();}}Как и шаблоны в языке С++, обобщенные типы в языке C#указываются в треугольных скобках после имени класса Обобщенныетипы принято обозначать прописными латинскими буквами или начинать спрописной буквы.В данном примере обобщенным является тип Т.В классе GenericClass1 объявлено поле данных i обобщенного типа Т.Класс содержит конструктор без параметров, в котором полю iприсваивается начальное значение.Для присваивания начальных значений переменным обобщенныхтипов в языке C# используется специальный оператор «default(тип)», а вкачестве параметра передается обобщенный тип.
Данный операторвозвращает значение по умолчанию в зависимости от того, какой тип будетподставлен в обобщенный тип, например для числовых типов это 0, дляссылочных типов – null.Метод SetValue присваивает значение полю i. Метод ToStringвозвращает строковое представление поля i.Этот обобщенный класс фактически является контейнером для одногополя обобщенного типа. На практике использование такого контейнера неимеет большой необходимости, данный пример является учебным.















