[Перевод] С# для AS3 разработчиков. Часть 5: Статические классы, Деструкторы и Приёмы для работы с конструкторами

imageПеревод статьи From AS3 to C#, Part 5: Static Classes, Destructors, and Constructor Tricks

В прошлый раз мы с вами рассмотрели абстрактные классы, но уже на этой неделе мы обсудим даже более абстрактный тип классов (чем абстрактные классы): статические классы. Так же, мы рассмотрим анти-конструкторы C#, которые более известны, как «деструкторы», и, в дополнение ко всему, мы рассмотрим некоторые забавные трюки при работе с конструкторами классов.

Статические классы

Давайте начнём сегодняшнюю статью с «даже более абстрактных» классов: статических классов. Работая с абстрактными классами, вы всё ещё можете расширять их и создавать экземпляры дочерних классов:

abstract class Shape { } class Square: Shape // legal { } new Shape (); // illegal new Square (); // legal Работая со статическими классами, вы не можете ни инстанциировать, ни наследовать их. Вы никогда не сможете создать экземпляр подобного класса:

static class Shape { } class Square: Shape // illegal { } new Shape (); // illegal new Square (); // illegal Но для чего вообще могут понадобиться подобные классы? Подобные классы могут быть хорошим местом для хранения статических функций, полей и свойств. И, так как вы не можете создавать экземпляры подобных классов, в них запрещено использование не статических полей любых типов данных. Конструкторы экземпляров класса так же запрещены, т.к. класс автоматически приравнивается к sealed классам. Довольно популярный пример использования подобных классов — класс Math. Вам вряд ли когда-либо нужно будет создать экземпляр этого класса, но внутри него содержится большое количество полезных статических функций (например Abs) и полей (например PI). Вот, как может выглядеть реализация подобного класса:

public static class Math { // remember that 'const' is automatically static // also, this would surely have more precision public const double PI = 3.1415926; public static double Abs (double value) { return value >= 0? value: -value; } } new Math (); // illegal В AS3 по-умолчанию нет поддержки статических классов на этапе компиляции, но вы можете обойти это ограничение, используя проверки на этапе проигрывания (run-time). Всё, что вам нужно будет сделать — это объявить класс, как final, и всегда бросать ошибку в конструкторе этого класса:

public final class Math { public static const PI: Number = 3.1415926; public function Math () { throw new Error («Math is static»); } public static function abs (value: Number): Number { return value >= 0? value: -value; } } new Math (); // legal, but throws an exception Деструкторы

Следующим пунктом в сегодняшней программе идут деструкторы, которые являются «анти-конструкторами», потому что они отвечают за уничтожение класса, а не за его создание, как в случае с обычными конструкторами. Деструкторы вызываются сборщиками мусора (Garbage Collector) непосредственно перед тем, как объект освобождает занимаемую им память. Вот, как они выглядят:

class TemporaryFile { ~TemporaryFile () { // cleanup code goes here } } Для создания деструктора, достаточно добавить ~ к имени класса. Деструктор может быть только один, и с ним нельзя использовать модификаторы доступа. Обычно, необходимости в создании деструкторов нет, но в некоторых случаях они могут быть полезными, в качестве способа очистки ресурсов после использования класса. В примере ниже деструктор используется для удаления из операционной системы временного файла, который в другом случае не будет удалён, т.к. GC никогда не сделает этого:

using System.IO; class TemporaryFile { public String Path { get; private set; } TemporaryFile (String path) { Path = path; File.Create (path); } ~TemporaryFile () { File.Delete (Path); } } // Create the temporary file TemporaryFile temp = new TemporaryFile (»/path/to/temp/file»); // … use the temporary file // Remove the last reference to the TemporaryFile instance // GC will now collect temp, call the destructor, and delete the file temp = null; В данном примере класс TemporaryFile создаёт файл в конструкторе экземпляра класса, и удаляет файл, когда на экземпляр класса нет ссылок и класс готов быть собранным GC, чтобы освободить память. В AS3 нет функций, которые бы вызывались, когда экземпляр класса готов быть собранным GC. Обычно, чтобы реализовать подобное поведение, необходимо вручную создавать и вызывать «псевдо-деструкторы» (обычно их называют dispose или destroy):

import flash.filesystem; class TemporaryFile { private var _path: String; public function get path (): String { return _path; } public function set path (p: String): void { _path = p; } private var _file: File; function TemporaryFile (path: String) { _path = path; _file = new File (path); var stream: FileStream = new FileStream (); stream.open (_file, FileMode.WRITE); } function dispose (): void { _file.deleteFile (); } } // Create the temporary file var temp: TemporaryFile = new TemporaryFile (»/path/to/temp/file»); // … use the temporary file // Manually call dispose () to delete the temporary file temp.dispose (); // Remove the last reference to the TemporaryFile instance // GC will now collect temp temp = null; Трюки при работе с конструкторами

Последней темой на сегодня будут трюки при работе с конструкторами. Мы уже разбирали способ вызова конструктора базового класса, используя ключевое слово base (аналогично использованию ключевого слова super в AS3):

class Polygon { Polygon (int numSides) { } } class Triangle: Polygon { Triangle () : base (3) // call the Polygon constructor { } } Так же, мы рассматривали возможность создания более чем одного конструктора, используя «перегрузку»:

class Vector3 { double X; double Y; double Z; Vector3() { X = 0; Y = 0; Z = 0; } Vector3(double x, double y, double z) { X = x; Y = y; Z = z; } Vector3(Vector3 vec) { X = vec.X; Y = vec.Y; Z = vec.Z; } } Vector3 v1 = new Vector3(); // (0, 0, 0) Vector3 v2 = new Vector3(1, 2, 3); // (1, 2, 3) Vector3 v3 = new Vector3(v2); // (1, 2, 3) Обычно этот способ приводит к дублированию кода внутри конструкторов. Но, т.к. версия конструктора, которая принимает 3 параметра наиболее общая из всех, то можно просто вызывать её из 2 других конструкторов:

class Vector3 { double X; double Y; double Z; Vector3() : this (0, 0, 0) { } Vector3(double x, double y, double z) { X = x; Y = y; Z = z; } Vector3(Vector3 vec) : this (vec.X, vec.Y, vec.Z) { } } Vector3 v1 = new Vector3(); // (0, 0, 0) Vector3 v2 = new Vector3(1, 2, 3); // (1, 2, 3) Vector3 v3 = new Vector3(v2); // (1, 2, 3) Мы можем использовать this () для вызова других конструкторов в рамках нашего класса (по аналогии с base (), что позволяло вызывать конструктор родительского класса). И снова, в AS3 не было подобного функционала по-умолчанию, поэтому его приходилось «эмулировать» с помощью статических псевдо-конструкторов, которые вызывали функции наподобие init/setup/contruct у создаваемых объектов:

class Vector3 { var x: Number; var y: Number; var z: Number; function Vector3() { init (0, 0, 0); } // pseudo-constructor static function fromComponents (x: Number, y: Number, z: Number) { var ret: Vector3 = new Vector3(); ret.init (x, y, z); return ret; } // pseudo-constructor static function fromVector (Vector3 vec) { var ret: Vector3 = new Vector3(); ret.init (vec.X, vec.Y, vec.Z); return ret; } // helper function function init (x: Number, y: Number, z: Number): void { this.x = x; this.y = y; this.z = z; } } var v1: Vector3 = new Vector3(); // (0, 0, 0) var v2: Vector3 = Vector3.fromComponents (1, 2, 3); // (1, 2, 3) var v3: Vector3 = Vector3.fromVector (v2); // (1, 2, 3) На этом мы сегодня закончим и, как обычно, в завершении статьи мы сравним описанные сегодня особенности работы с C# и AS3:

//////// // C# // //////// // Static class public static class MathHelpers { public const double DegreesToRadians = Math.PI / 180.0; public const double RadiansToDegrees = 180.0 / Math.PI; public static double ConvertDegreesToRadians (double degrees) { return degrees * DegreesToRadians; } public static double ConvertRadiansToDegrees (double radians) { return radians * RadiansToDegrees; } } // Class with a destructor class TemporaryFile { public String Path { get; private set; } TemporaryFile (String path) { Path = path; File.Create (path); } // Destructor ~TemporaryFile () { File.Delete (Path); } } // Class with shared constructor code class Vector3 { double X; double Y; double Z; Vector3() : this (0, 0, 0) { } // shared constructor code Vector3(double x, double y, double z) { X = x; Y = y; Z = z; } Vector3(Vector3 vec) : this (vec.X, vec.Y, vec.Z) { } } ///////// // AS3 // ///////// // Static class — runtime only public class MathHelpers { public static const DegreesToRadians: Number = Math.PI / 180.0; public static const RadiansToDegrees: Number = 180.0 / Math.PI; public function MathHelpers () { throw new Error («MathHelpers is static»); } public static function ConvertDegreesToRadians (degrees: Number): Number { return degrees * DegreesToRadians; } public static function ConvertRadiansToDegrees (radians: Number): Number { return radians * RadiansToDegrees; } } // Class with a destructor class TemporaryFile { private var _path: String; public function get path (): String { return _path; } public function set path (p: String): void { _path = p; } private var _file: File; function TemporaryFile (path: String) { _path = path; _file = new File (path); var stream: FileStream = new FileStream (); stream.open (_file, FileMode.WRITE); } // Destructor — must be called manually function dispose (): void { _file.deleteFile (); } } // Class with shared constructor code class Vector3 { var x: Number; var y: Number; var z: Number; function Vector3() { init (0, 0, 0); } static function fromComponents (x: Number, y: Number, z: Number) { var ret: Vector3 = new Vector3(); ret.init (x, y, z); return ret; } static function fromVector (Vector3 vec) { var ret: Vector3 = new Vector3(); ret.init (vec.X, vec.Y, vec.Z); return ret; } // shared constructor code — helper function required function init (x: Number, y: Number, z: Number): void { this.x = x; this.y = y; this.z = z; } }

© Habrahabr.ru