C# Language
Конструкторы и финализаторы
Поиск…
Вступление
Конструкторы - это методы в классе, которые вызывается при создании экземпляра этого класса. Их основная ответственность заключается в том, чтобы оставить новый объект в полезном и согласованном состоянии.
Деструкторы / Финализаторы - это методы в классе, которые вызывается, когда экземпляр этого объекта уничтожается. В C # они редко явно пишутся / используются.
замечания
На C # фактически нет деструкторов, а скорее финализаторы, которые используют синтаксис деструктора стиля C ++. Указание деструктора переопределяет метод Object.Finalize()
который нельзя вызвать напрямую.
В отличие от других языков с похожим синтаксисом, эти методы не вызывают, когда объекты выходят из области действия, но вызывается, когда запускается сборщик мусора, который происходит при определенных условиях . Таким образом, они не гарантируются в каком-либо конкретном порядке.
Финализаторы должны нести ответственность за очистку неуправляемых ресурсов только (указатели , полученные с помощью класса Marshal, полученные через р / Invoke (системные вызовы) или сырые указатели , используемые в небезопасных блоках). Чтобы очистить управляемые ресурсы, просмотрите IDisposable, шаблон Dispose и инструкцию using
.
(Дальнейшее чтение: Когда я должен создать деструктор? )
Конструктор по умолчанию
Когда тип определен без конструктора:
public class Animal
{
}
то компилятор генерирует конструктор по умолчанию, эквивалентный следующему:
public class Animal
{
public Animal() {}
}
Определение любого конструктора для типа будет подавлять генерацию конструктора по умолчанию. Если тип был определен следующим образом:
public class Animal
{
public Animal(string name) {}
}
то Animal
может быть создано только путем вызова объявленного конструктора.
// This is valid
var myAnimal = new Animal("Fluffy");
// This fails to compile
var unnamedAnimal = new Animal();
Во втором примере компилятор отобразит сообщение об ошибке:
«Animal» не содержит конструктор, который принимает 0 аргументов
Если вы хотите, чтобы класс имел как конструктор без параметров, так и конструктор, который принимает параметр, вы можете сделать это, явно реализуя оба конструктора.
public class Animal
{
public Animal() {} //Equivalent to a default constructor.
public Animal(string name) {}
}
Компилятор не сможет создать конструктор по умолчанию, если класс расширяет другой класс, который не имеет конструктора без параметров. Например, если бы у нас было классное Creature
:
public class Creature
{
public Creature(Genus genus) {}
}
то Animal
определяется как class Animal : Creature {}
не будет компилироваться.
Вызов конструктора из другого конструктора
public class Animal
{
public string Name { get; set; }
public Animal() : this("Dog")
{
}
public Animal(string name)
{
Name = name;
}
}
var dog = new Animal(); // dog.Name will be set to "Dog" by default.
var cat = new Animal("Cat"); // cat.Name is "Cat", the empty constructor is not called.
Статический конструктор
Статический конструктор называется первым, когда инициализируется любой член типа, вызывается статический член класса или статический метод. Статический конструктор является потокобезопасным. Статический конструктор обычно используется для:
- Инициализируйте статическое состояние, то есть состояние, которое используется для разных экземпляров одного и того же класса.
- Создать синглтон
Пример:
class Animal
{
// * A static constructor is executed only once,
// when a class is first accessed.
// * A static constructor cannot have any access modifiers
// * A static constructor cannot have any parameters
static Animal()
{
Console.WriteLine("Animal initialized");
}
// Instance constructor, this is executed every time the class is created
public Animal()
{
Console.WriteLine("Animal created");
}
public static void Yawn()
{
Console.WriteLine("Yawn!");
}
}
var turtle = new Animal();
var giraffe = new Animal();
Выход:
Инициализировано
Животное создано
Животное создано
Если первый вызов относится к статическому методу, статический конструктор вызывается без конструктора экземпляра. Это нормально, потому что статический метод никак не может получить доступ к состоянию экземпляра.
Animal.Yawn();
Это приведет к выводу:
Инициализировано
Зевать!
См. Также Исключения в статических конструкторах и Generic Static Constructors .
Пример Singleton:
public class SessionManager
{
public static SessionManager Instance;
static SessionManager()
{
Instance = new SessionManager();
}
}
Вызов конструктора базового класса
Вызывается конструктор базового класса перед выполнением конструктора производного класса. Например, если Mammal
расширяет Animal
, тогда код, содержащийся в конструкторе Animal
вызывается первым при создании экземпляра Mammal
.
Если производный класс явно не указывает, какой конструктор базового класса следует вызывать, компилятор принимает конструктор без параметров.
public class Animal
{
public Animal() { Console.WriteLine("An unknown animal gets born."); }
public Animal(string name) { Console.WriteLine(name + " gets born"); }
}
public class Mammal : Animal
{
public Mammal(string name)
{
Console.WriteLine(name + " is a mammal.");
}
}
В этом случае создание экземпляра Mammal
путем вызова new Mammal("George the Cat")
напечатает
Появляется неизвестное животное.
Джордж Кошка - млекопитающее.
Вызов другого конструктора базового класса выполняется путем размещения : base(args)
между сигнатурой конструктора и его телом:
public class Mammal : Animal
{
public Mammal(string name) : base(name)
{
Console.WriteLine(name + " is a mammal.");
}
}
Вызов new Mammal("George the Cat")
теперь напечатает:
Джордж Кот рождается.
Джордж Кошка - млекопитающее.
Финализаторы на производных классах
Когда графический объект завершен, порядок является обратной конструкцией. Например, супертип завершается до базового типа, как показывает следующий код:
class TheBaseClass
{
~TheBaseClass()
{
Console.WriteLine("Base class finalized!");
}
}
class TheDerivedClass : TheBaseClass
{
~TheDerivedClass()
{
Console.WriteLine("Derived class finalized!");
}
}
//Don't assign to a variable
//to make the object unreachable
new TheDerivedClass();
//Just to make the example work;
//this is otherwise NOT recommended!
GC.Collect();
//Derived class finalized!
//Base class finalized!
Шаблон конструктора Singleton
public class SingletonClass
{
public static SingletonClass Instance { get; } = new SingletonClass();
private SingletonClass()
{
// Put custom constructor code here
}
}
Поскольку конструктор является закрытым, никакие новые экземпляры SingletonClass
могут быть сделаны путем использования кода. Единственный способ доступа к единственному экземпляру SingletonClass
- использование статического свойства SingletonClass.Instance
.
Instance
присваивается статический конструктор, создаваемый компилятором C #. Среда выполнения .NET гарантирует, что статический конструктор запускается не более одного раза и запускается до того, как Instance
будет сначала прочитан. Поэтому все проблемы синхронизации и инициализации выполняются во время выполнения.
Обратите внимание: если статический конструктор не работает, класс Singleton
становится непригодным для жизни AppDomain.
Кроме того, статический конструктор не гарантированно запускается во время первого доступа Instance
. Скорее, он будет работать в какой-то момент до этого . Это делает время, при котором инициализация происходит без детерминизма. В практических случаях JIT часто вызывает статический конструктор во время компиляции (а не выполнения) метода, ссылающегося на Instance
. Это оптимизация производительности.
См. Страницу « Режимы использования Singleton» для других способов реализации одноэлементного шаблона.
Принудительный вызов статического конструктора
В то время как статические конструкторы всегда вызывают перед первым использованием типа, иногда полезно иметь возможность заставить их быть вызванными, а класс RuntimeHelpers
предоставляет для этого помощника:
using System.Runtime.CompilerServices;
// ...
RuntimeHelpers.RunClassConstructor(typeof(Foo).TypeHandle);
Примечание : вся статическая инициализация (инициализаторы полей, например) будет выполняться не только самим конструктором.
Потенциальные способы использования : принудительная инициализация во время заставки в приложении пользовательского интерфейса или обеспечение того, что статический конструктор не сбой в модульном тесте.
Вызов виртуальных методов в конструкторе
В отличие от C ++ в C # вы можете вызвать виртуальный метод из конструктора классов (ОК, вы также можете на C ++, но поведение сначала удивительно). Например:
abstract class Base
{
protected Base()
{
_obj = CreateAnother();
}
protected virtual AnotherBase CreateAnother()
{
return new AnotherBase();
}
private readonly AnotherBase _obj;
}
sealed class Derived : Base
{
public Derived() { }
protected override AnotherBase CreateAnother()
{
return new AnotherDerived();
}
}
var test = new Derived();
// test._obj is AnotherDerived
Если вы исходите из фона C ++, это удивительно, конструктор базового класса уже видит таблицу виртуальных методов производного класса!
Будьте осторожны : производный класс еще не полностью инициализирован (его конструктор будет выполняться после конструктора базового класса), и этот метод опасен (для этого также существует предупреждение StyleCop). Обычно это считается плохой практикой.
Общие статические конструкторы
Если тип, на котором объявлен статический конструктор, является общим, статический конструктор будет вызываться один раз для каждой уникальной комбинации общих аргументов.
class Animal<T>
{
static Animal()
{
Console.WriteLine(typeof(T).FullName);
}
public static void Yawn() { }
}
Animal<Object>.Yawn();
Animal<String>.Yawn();
Это приведет к выводу:
System.Object
System.String
См. Также Как работают статические конструкторы для общих типов?
Исключения в статических конструкторах
Если статический конструктор генерирует исключение, он никогда не будет повторен. Тип непригоден для жизни AppDomain. Любые дополнительные применения этого типа будут вызывать TypeInitializationException
обернутое вокруг исходного исключения.
public class Animal
{
static Animal()
{
Console.WriteLine("Static ctor");
throw new Exception();
}
public static void Yawn() {}
}
try
{
Animal.Yawn();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
try
{
Animal.Yawn();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
Это приведет к выводу:
Статический калькулятор
System.TypeInitializationException: инициализатор типа для «Animal» сделал исключение. ---> System.Exception: выбрано исключение типа «System.Exception».
[...]
System.TypeInitializationException: инициализатор типа для «Animal» сделал исключение. ---> System.Exception: выбрано исключение типа «System.Exception».
где вы можете видеть, что фактический конструктор выполняется только один раз, и исключение повторно используется.
Инициализация конструктора и объектов
Устанавливается ли присвоение значения свойства до или после конструктора класса?
public class TestClass
{
public int TestProperty { get; set; } = 2;
public TestClass()
{
if (TestProperty == 1)
{
Console.WriteLine("Shall this be executed?");
}
if (TestProperty == 2)
{
Console.WriteLine("Or shall this be executed");
}
}
}
var testInstance = new TestClass() { TestProperty = 1 };
В приведенном выше TestProperty
значение TestProperty
равно 1
в конструкторе класса или после конструктора класса?
Присвоение значений свойств в создании экземпляра следующим образом:
var testInstance = new TestClass() {TestProperty = 1};
Будет выполнен после запуска конструктора. Однако инициализировать значение свойства в свойстве класса в C # 6.0 следующим образом:
public class TestClass
{
public int TestProperty { get; set; } = 2;
public TestClass()
{
}
}
будет выполняться до запуска конструктора.
Объединение двух концепций выше в одном примере:
public class TestClass
{
public int TestProperty { get; set; } = 2;
public TestClass()
{
if (TestProperty == 1)
{
Console.WriteLine("Shall this be executed?");
}
if (TestProperty == 2)
{
Console.WriteLine("Or shall this be executed");
}
}
}
static void Main(string[] args)
{
var testInstance = new TestClass() { TestProperty = 1 };
Console.WriteLine(testInstance.TestProperty); //resulting in 1
}
Конечный результат:
"Or shall this be executed"
"1"
Объяснение:
Значение TestProperty
сначала будет назначено как 2
, тогда будет TestClass
конструктор TestClass
, в результате чего будет напечатана
"Or shall this be executed"
И тогда TestProperty
будет назначен как 1
из-за new TestClass() { TestProperty = 1 }
, в результате чего окончательное значение для TestProperty
напечатанное Console.WriteLine(testInstance.TestProperty)
будет
"1"