Lecture11 (1133568), страница 3
Текст из файла (страница 3)
При этом результатомсоответствующей операции являетсяконкатенация его и результата примененияметода toString() к другому операнду вНекоторые операторы в C# можнопереопределить (перекрыть) для данногопользовательского типа. Переопределяемыйоператор всегда имеет модификатор static.Переопределяемые унарные операторы (ихединственный параметр должен иметь тот тип,в рамках которого они переопределяются, илиобъемлющий тип): +, -, !, ~ (в качестве типарезультата могут иметь любой тип), ++,-- (типих результата может быть только подтипомобъемлющего), true, false (тип результатаbool).Переопределяемые бинарные операторы (хотябы один из их параметров должен иметьпорядке следования операндов.объемлющий тип, а возвращать они могутрезультат любого типа): +, -, *, /, %, &, |, ^, <<,>>, ==, !=, <, >, <=, >=.
Для операторов сдвига <<и >> ограничения более жесткие — первый ихпараметр должен иметь объемлющий тип, авторой быть типа int.Можно определять также операторыприведения к другому типу или приведения издругого типа, причем можно объявить такоеприведение неявным с помощью модификатораimplicit, чтобы компилятор сам вставлял еготам, где оно необходимо для соблюденияправил соответствия типов. Иначе надоиспользовать модификатор explicit и всегдаявно приводить один тип к другому.Некоторые операторы можно определять толькопарами — таковы true и false, == и !=, < и >,<= и >=.Операторы true и false служат для неявногопреобразования объектов данного типа ксоответствующим логическим значениям.Если в типе T определяются операторы & и |,возвращающие значение этого же типа, а такжеоператоры true и false, то к объектам типаможно применять условные логическиеоператоры && и ||.
Их поведение в этом случаеможет быть описано соотношениями (x && y)= (T.false(x)? x : (x & y)) и (x || y) =(T.true(x)? x : (x | y)).Аналогичным образом автоматическипереопределяются составные операторыприсваивания, если переопределены операторы+, -, *, /, %, &, |, ^, << или >>.Ниже приведен пример переопределения ииспользования операторов. Обратите внимание,что оператор приведения типа MyInt в intдействует неявно, а для обратного переходатребуется явное приведение.Другая тонкость — необходимость приведенияобъектов типа MyInt к object в методеAreEqual — если этого не сделать, то приобращении к оператору == возникнетбесконечный цикл, поскольку сравнение i1 ==null тоже будет интерпретироваться как вызовэтого оператора.using System;public class MyInt{int n = 0;public MyInt(int n) { this.n = n; }public override bool Equals(object obj){MyInt o = obj as MyInt;if (o == null) return false;return o.n == n;}public override int GetHashCode(){ return n; }public override string ToString(){ return n.ToString(); }public static bool AreEqual(MyInt i1, MyInt i2){if ((object)i1 == null)return ((object)i2 == null);else return i1.Equals(i2);}public static bool operator ==(MyInt i1, MyInt i2){ return AreEqual(i1, i2); }public static bool operator !=(MyInt i1, MyInt i2){ return !AreEqual(i1, i2); }public static bool operator true(MyInt i){ return i.n > 0; }public static bool operator false(MyInt i){ return i.n <= 0; }public static MyInt operator ++(MyInt i){ return new MyInt(i.n + 1); }public static MyInt operator -(MyInt i){ return new MyInt(i.n - 1); }public static MyInt operator &(MyInt i1, MyInt i2){ return new MyInt(i1.n & i2.n); }public static MyInt operator |(MyInt i1, MyInt i2){ return new MyInt(i1.n | i2.n); }public static implicit operator int(MyInt i){ return i.n; }public static explicit operatorMyInt (int i){ return new MyInt(i); }public static void Main(){MyInt n = (MyInt)5;MyInt k = (MyInt)(n - 3 * n);Console.WriteLine("k = " + k +" , n = " + n);Console.WriteLine("n == n : " +(n == n));Console.WriteLine("n == k : " +(n == k));Console.WriteLine("(++k) && (n++) : " +((++k) && (n++)));Console.WriteLine("k = " + k +" , n = " + n);Console.WriteLine("(++n) && (k++) : " +((++n) && (k++)));Console.WriteLine("k = " + k +" , n = " + n);}}Аналогом деструктора в Java является методprotected void finalize(), который можноперегрузить для данного класса.Так же, как и деструктор в C#, этот методвызывается на некотором шаге уничтоженияобъекта после того, как тот был помеченсборщиком мусора как неиспользуемый.Деструктор предназначен для освобождениякаких-либо ресурсов, связанных с объектом ине освобождаемых автоматически средой .NET,либо для оптимизации использования ресурсовза счет их явного освобождения.Деструктор вызывается автоматически приуничтожении объекта в ходе работы механизмауправления памятью .NET.
В этот моментобъект уже должен быть помечен сборщикоммусора как неиспользуемый.Деструктор оформляется как особый метод, безвозвращаемого значения и с именем,получающимся добавлением префикса ‘~’ кимени классаusing System;public class MyFileReader{java.io.FileReader input;}public class MyFileStream{System.IO.FileStream input;public MyFileReader(String path)throws FileNotFoundException{input = new java.io.FileReader(new java.io.File(path));}public MyFileStream(string path){input = System.IO.File.Open(path, System.IO.FileMode.Open);}protected void finalize(){System.out.println("Destructor");try { input.close(); }catch (IOException e){e.printStackTrace();}}~MyFileStream(){Console.WriteLine("Destructor");input.Close();}}Инициализаторы представляют собой блокикода, заключенные в фигурные скобки ирасположенные непосредственно внутридекларации класса.Эти блоки выполняются вместе синициализаторами отдельных полей —выражениями, которые написаны после знака =в объявлениях полей — при построении объектаданного класса, в порядке их расположения вдекларации.Статические инициализаторы — такие жеблоки, помеченные модификатором static —выполняются вместе с инициализаторамистатических полей по тем же правилам вмомент первой загрузки класса в Java-машину.Статический конструктор классапредставляет собой блок кода, выполняемыйпри первой загрузке класса в среду .NET, т.е.
вмомент первого использования этого класса впрограмме. Это аналог статическогоинициализатора в Java.Оформляется он как конструктор смодификатором static.using System;public class A{static{System.out.println("Loading A");}static int x = 1;static{System.out.println("x = " + x);x++;}public class A{static A(){Console.WriteLine("LoadingConsole.WriteLine("x = " +x++;y = x + 3;Console.WriteLine("x = " +Console.WriteLine("y = " +}A");x);x);y);static int x = 1;static int y = 2;static int y = 2;public static void Main() {}static{y = x + 3;System.out.println("x = " + x);System.out.println("y = " + y);}}public static void main(String[] args){}}Приведенный выше код выдает результатПриведенный выше код выдает результатLoading Ax = 1x = 2y = 5Loading Ax = 1x = 2y = 5В Java нестатические вложенные типытрактуются очень специфическим образом —каждый объект такого типа считаетсяпривязанным к определенному объектуобъемлющего типа.
У нестатическогоВ C# модификатор static у класса, все равно,вложенного в другой или нет, обозначает, чтоэтот класс является контейнером набораконстант и статических операций. Все егоэлементы должны быть static.вложенного типа есть как бы поле, хранящеессылку на объект объемлющего типа.Такая конструкция используется, например, дляопределения классов итераторов для коллекций— объект-итератор всегда связан с объектомколлекцией, которую он итерирует. В то жевремя, пользователю не нужно знать, какогоименно типа данный итератор, — достаточно,что он реализует общий интерфейс всехитераторов, позволяющий проверить, есть лиеще объекты, и получить следующий объект.Получить этот объект внутри декларациивложенного типа можно с помощьюконструкции ClassName.this, где ClassName —имя объемлющего типа.При создании объекта такого вложенногокласса необходимо указать объектобъемлющего класса, к которому тот будетпривязан.public class ContainingClass{static int counter = 1;static int ecounter = 1;int id = counter++;class EmbeddedClass{int eid = ecounter++;public String toString() {return "" +ContainingClass.this.id +'.' + eid;}}public String toString(){return "" + id;}public static void main(String[] args){ContainingClassc = new ContainingClass(), c1 = new ContainingClass();System.out.println(c);System.out.println(c1);EmbeddedClasse = c.new EmbeddedClass(), e1 = c.new EmbeddedClass(), e2 = c1.new EmbeddedClass();System.out.println(e);System.out.println(e1);System.out.println(e2);}}В C# класс может определить различныереализации для операций (методов, свойств,индексеров, событий) с одинаковой сигнатурой,если они декларированы в различныхреализуемых классом интерфейсах.Для этого при определении таких операцийнужно указывать имя интерфейса в качестверасширения их имени.using System;public interface I1{void m();}public interface I2{void m();}public class A : I1, I2{public void m(){Console.WriteLine("A.m() called");}void I1.m(){Console.WriteLine("I1.m() defined in A called");}void I2.m(){Console.WriteLine("I2.m() defined in A called");}public static void Main(){A f = new A();I1 i1 = f;I2 i2 = f;f.m();i1.m();i2.m();}}Результат работы приведенного выше примераследующий.A.m() calledI1.m() defined in A calledI2.m() defined in A calledПоследовательность выполнения инициализаторов полей и конструкторов классов-предков инаследников при построении объектов в Java и C# различается достаточно сильно.О правилах, определяющих эту последовательность, можно судить по результатам работыследующих примеров.using System;public class A{static{System.out.println("Static initializer of A");}public class A{static A(){Console.WriteLine("Static constructor of A");}{System.out.println("Initializer of A");}static int sinit(){System.out.println("Static field initializer of A");return 0;}static int sinit(){Console.WriteLine("Static field initializer of A");return 0;}static int init(){System.out.println("Field initializer of A");return 0;}static int init(){Console.WriteLine("Field initializer of A");return 0;}static int sf = sinit();int f = init();static int sf = sinit();int f = init();public A(){System.out.println("Constructor of A");}public A(){Console.WriteLine("Constructor of A");}}}public class B extends A{static int sf = sinit();int f = init();public class B : A{static int sf = sinit();int f = init();static{System.out.println("Static initializer of B");}static B(){Console.WriteLine("Static constructor of B");}{System.out.println("Initializer of B");}static int sinit(){System.out.println("Static field initializer of B");return 0;}static int sinit(){Console.WriteLine("Static field initializer of B");return 0;}static int init(){static int init(){Console.WriteLine("Field initializer of B");return 0;System.out.println("Field initializer of B");return 0;}}public B(){System.out.println("Constructor of B");}public B(){Console.WriteLine("Constructor of B");}}}public class C{public static void main(String[] args){B b = new B();}}public class C{public static void Main(){B b = new B();}}Результат работы приведенного примера такой.Результат работы приведенного примера такой.Static initializer of AStatic field initializer of AStatic field initializer of BStatic initializer of BInitializer of AField initializer of AConstructor of AField initializer of BInitializer of BConstructor of BStatic field initializer of BStatic constructor of BField initializer of BStatic field initializer of AStatic constructor of AField initializer of AConstructor of AConstructor of BВ Java элемент типа, помимо доступности,указываемой с помощью модификаторовprivate, protected и public, может иметьпакетную доступность.