Перевод статьи From AS3 to C#, Part 5: Static Classes, Destructors, and Constructor Tricks
В прошлый раз мы с вами рассмотрели абстрактные классы, но уже на этой неделе мы обсудим даже более абстрактный тип классов (чем абстрактные классы): статические классы. Так же, мы рассмотрим анти-конструкторы C#, которые более известны, как “деструкторы”, и, в дополнение ко всему, мы рассмотрим некоторые забавные трюки при работе с конструкторами классов.
Статические классы
Давайте начнём сегодняшнюю статью с “даже более абстрактных” классов: статических классов. Работая с абстрактными классами, вы всё ещё можете расширять их и создавать экземпляры дочерних классов:
1 2 3 4 5 6 7 8 9 10 11 |
abstract class Shape { } class Square : Shape // legal { } new Shape(); // illegal new Square(); // legal |
Работая со статическими классами, вы не можете ни инстанциировать, ни наследовать их. Вы никогда не сможете создать экземпляр подобного класса:
1 2 3 4 5 6 7 8 9 10 11 |
static class Shape { } class Square : Shape // illegal { } new Shape(); // illegal new Square(); // illegal |
Но для чего вообще могут понадобиться подобные классы? Подобные классы могут быть хорошим местом для хранения статических функций, полей и свойств. И, так как вы не можете создавать экземпляры подобных классов, в них запрещено использование не статических полей любых типов данных. Конструкторы экземпляров класса так же запрещены, т.к. класс автоматически приравнивается к sealed классам. Довольно популярный пример использования подобных классов – класс Math. Вам вряд ли когда-либо нужно будет создать экземпляр этого класса, но внутри него содержится большое количество полезных статических функций (например Abs) и полей (например PI). Вот, как может выглядеть реализация подобного класса:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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, и всегда бросать ошибку в конструкторе этого класса:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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) непосредственно перед тем, как объект освобождает занимаемую им память. Вот, как они выглядят:
1 2 3 4 5 6 7 8 |
class TemporaryFile { ~TemporaryFile() { // cleanup code goes here } } |
Для создания деструктора, достаточно добавить ~ к имени класса. Деструктор может быть только один, и с ним нельзя использовать модификаторы доступа. Обычно, необходимости в создании деструкторов нет, но в некоторых случаях они могут быть полезными, в качестве способа очистки ресурсов после использования класса. В примере ниже деструктор используется для удаления из операционной системы временного файла, который в другом случае не будет удалён, т.к. GC никогда не сделает этого:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
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):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
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):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Polygon { Polygon(int numSides) { } } class Triangle : Polygon { Triangle() : base(3) // call the Polygon constructor { } } |
Так же, мы рассматривали возможность создания более чем одного конструктора, используя “перегрузку”:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
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 других конструкторов:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
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 у создаваемых объектов:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
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#, которых не было в AS3!