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

http://jacksondunstan.com/articles/2737
  • Перевод
  • Tutorial
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;
    }
}

Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Комментарии 2
  • +2
    По поводу деструкторов: никогда не используйте деструкторы — потому что вы не можете гарантировать, в какой момент времени они будут вызваны!

    Если некоторый ресурс полностью управляемый (т.е. существует исключительно в памяти приложения) — следует использовать интерфейс IDisposable и его метод Dispose (кстати, почему про него нет ни слова — хотя это прямой аналог происходящему в AS3?)

    Если некоторый объект содержит неуправляемые ресурсы — то тоже достаточно метода Dispose, который вызовет другой метод Dispose по цепочке.

    Единственный случай, когда требуется взаимодействие со сборщиком мусора — это полностью неуправляемый объект. Такой объект должен наследовать класс SafeHandle и перегружать его метод ReleaseHandle. Точка.

    PS

    А файл удалять лучше всего через указание флага FILE_FLAG_DELETE_ON_CLOSE — это гарантирует удаление файла силами ОС даже в случае завершения процесса через диспетчер задач — чего другие методы отложенного удаления не могут гарантировать в принципе.
    • +1
      Спасибо за такое подробное описание, я не был знаком с этой информацией, о которой вы рассказали.

      На счёт вашего вопроса о IDisposable: думаю, этот вопрос можно задать автору статьи (оригинала), возможно он тоже не знал о существовании подобного интерфейса (на сколько я понимаю, автор сам не так давно начал переход с AS3 на C# и тоже может допускать ошибки или иметь белые пятна в своих знаниях).

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.