C# Language
Konstruktörer och slutbehandlare
Sök…
Introduktion
Konstruktörer är metoder i en klass som åberopas när en instans av den klassen skapas. Deras huvudansvar är att lämna det nya objektet i ett användbart och konsekvent tillstånd.
Destruktorer / finaliserare är metoder i en klass som åberopas när en instans av detta förstörs. I C # skrivs / används de sällan uttryckligen.
Anmärkningar
C # har faktiskt inte förstörare, utan snarare slutförare som använder C ++ -stylt-syntax. Att ange en förstörare åsidosätter Object.Finalize()
som inte kan kallas direkt.
Till skillnad från andra språk med liknande syntax kallas inte dessa metoder när objekt går utanför räckvidden, utan kallas när Garbage Collector körs, vilket sker under vissa förhållanden . Som sådan är de inte garanterade att köra i någon särskild ordning.
Finalizers bör ansvara för sanering oövervakade resurser endast (pekare förvärvats via Marshal klassen, mottagna genom p / Invoke (systemanrop) eller råa pekare användas inom osäkra block). För att rensa upp hanterade resurser, läs igenom IDisposable, Disponera mönstret och det using
uttalandet.
(Ytterligare läsning: När ska jag skapa en förstörare? )
Standardkonstruktör
När en typ definieras utan konstruktör:
public class Animal
{
}
sedan genererar kompilatorn en standardkonstruktör motsvarande följande:
public class Animal
{
public Animal() {}
}
Definitionen av vilken konstruktör som helst för typen kommer att undertrycka standardkonstruktörgenerering. Om typen definierades enligt följande:
public class Animal
{
public Animal(string name) {}
}
då kunde ett Animal
bara skapas genom att ringa den deklarerade konstruktören.
// This is valid
var myAnimal = new Animal("Fluffy");
// This fails to compile
var unnamedAnimal = new Animal();
För det andra exemplet kommer kompilatorn att visa ett felmeddelande:
'Djur' innehåller inte en konstruktör som tar 0 argument
Om du vill att en klass ska ha både en parameterlös konstruktör och en konstruktör som tar en parameter kan du göra det genom att uttryckligen implementera båda konstruktorerna.
public class Animal
{
public Animal() {} //Equivalent to a default constructor.
public Animal(string name) {}
}
Kompilatorn kan inte generera en standardkonstruktör om klassen utvidgar en annan klass som inte har en parameterlös konstruktör. Om vi till exempel hade en klass Creature
:
public class Creature
{
public Creature(Genus genus) {}
}
då definierades Animal
som class Animal : Creature {}
skulle inte kompilera.
Ring en konstruktör från en annan konstruktör
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.
Statisk konstruktör
En statisk konstruktör kallas första gången någon medlem av en typ initialiseras, en statisk klassmedlem kallas eller en statisk metod. Den statiska konstruktören är gängsäker. En statisk konstruktör används vanligen för att:
- Initiera statiskt tillstånd, det vill säga tillstånd som delas över olika instanser av samma klass.
- Skapa en singleton
Exempel:
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();
Produktion:
Djur initialiserat
Djur skapat
Djur skapat
Om det första samtalet gäller en statisk metod, åberopas den statiska konstruktören utan instanskonstruktören. Det här är OK, eftersom den statiska metoden inte kan komma åt instansstatus ändå.
Animal.Yawn();
Detta kommer att matas ut:
Djur initialiserat
Gäspa!
Se även undantag från statiska konstruktörer och generiska statiska konstruktörer .
Exempel på Singleton:
public class SessionManager
{
public static SessionManager Instance;
static SessionManager()
{
Instance = new SessionManager();
}
}
Ringer baskonstruktören
En konstruktör av en basklass kallas innan en konstruktör av en härledd klass körs. Till exempel, om Mammal
utökar Animal
, kallas den kod som finns i konstruktören av Animal
först när en instans av ett Mammal
skapas.
Om en härledd klass inte uttryckligen anger vilken konstruktör för basklassen som ska anropas, antar kompilatorn den parameterlösa konstruktorn.
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.");
}
}
I det här fallet kommer det att trycka på ett Mammal
genom att kalla ett new Mammal("George the Cat")
Ett okänt djur föds.
George the Cat är ett däggdjur.
Att ringa en annan konstruktör av basklassen görs genom att placera : base(args)
mellan konstruktörens signatur och dess kropp:
public class Mammal : Animal
{
public Mammal(string name) : base(name)
{
Console.WriteLine(name + " is a mammal.");
}
}
Det new Mammal("George the Cat")
kommer nu att skrivas ut:
George the Cat blir född.
George the Cat är ett däggdjur.
Finaliserare på härledda klasser
När en objektgraf avslutas är ordningen omvänd konstruktion. Exempelvis avslutas supertypen innan bastypen som följande kod visar:
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 konstruktormönster
public class SingletonClass
{
public static SingletonClass Instance { get; } = new SingletonClass();
private SingletonClass()
{
// Put custom constructor code here
}
}
Eftersom konstruktören är privat kan inga nya fall av SingletonClass
göras genom att konsumera kod. Det enda sättet att komma åt SingletonClass
enskilda instanser är att använda den statiska egenskapen SingletonClass.Instance
.
Instance
tilldelas av en statisk konstruktör som C # -kompilatorn genererar. .NET-körtiden garanterar att den statiska konstruktorn körs högst en gång och körs innan Instance
först läses. Därför utförs alla problem med synkronisering och initialisering av körtiden.
Observera att om den statiska konstruktorn misslyckas blir Singleton
klassen permanent oanvändbar under AppDomains livslängd.
Dessutom garanteras inte att den statiska konstruktorn körs vid tidpunkten för första tillträde till Instance
. Snarare kommer den att köras någon gång innan det . Detta gör att den tidpunkt då initialisering sker inte-deterministisk. I praktiska fall kallar JIT ofta den statiska konstruktören under sammanställning (inte exekvering) av en metod som refererar till Instance
. Detta är en prestationsoptimering.
Se Singleton Implementations- sidan för andra sätt att implementera singleton-mönstret.
Tvingar en statisk konstruktör att ringas
Medan statiska konstruktörer alltid kallas före den första användningen av en typ är det ibland användbart att kunna tvinga dem att kallas och klassen RuntimeHelpers
ger en hjälpare för det:
using System.Runtime.CompilerServices;
// ...
RuntimeHelpers.RunClassConstructor(typeof(Foo).TypeHandle);
Anmärkning : All statisk initialisering (fältinitierare till exempel) kommer att köras, inte bara själva konstruktören.
Potentiella användningar : Tvinga initiering under stänkskärmen i en UI-applikation eller se till att en statisk konstruktör inte misslyckas i ett enhetstest.
Ringa virtuella metoder i konstruktör
Till skillnad från C ++ i C # kan du ringa en virtuell metod från klasskonstruktör (OK, du kan också i C ++ men beteende i början är överraskande). Till exempel:
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
Om du kommer från en C ++ bakgrund är det förvånande, basklasskonstruktören ser redan härledd klass virtuell metodtabell!
Var försiktig : härledd klass kanske inte är fullt initialiserad ännu (dess konstruktör kommer att utföras efter basklasskonstruktör) och den här tekniken är farlig (det finns också en StyleCop-varning för detta). Vanligtvis betraktas detta som dålig praxis.
Generiska statiska konstruktörer
Om den typ som den statiska konstruktören deklareras är generisk kommer den statiska konstruktören att kallas en gång för varje unik kombination av generiska argument.
class Animal<T>
{
static Animal()
{
Console.WriteLine(typeof(T).FullName);
}
public static void Yawn() { }
}
Animal<Object>.Yawn();
Animal<String>.Yawn();
Detta kommer att matas ut:
System.Object
System.String
Se även Hur fungerar statiska konstruktörer för generiska typer?
Undantag i statiska konstruktörer
Om en statisk konstruktör kastar ett undantag, försöks den aldrig igen. Typen är oanvändbar under AppDomains livstid. Eventuella ytterligare användningar av typen kommer att höja en TypeInitializationException
lindad runt det ursprungliga undantaget.
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());
}
Detta kommer att matas ut:
Statisk ctor
System.TypeInitializationException: Typinitieraren för 'Animal' kastade ett undantag. ---> System.Exception: Undantag av typen 'System.Exception' kastades.
[...]
System.TypeInitializationException: Typinitieraren för 'Animal' kastade ett undantag. ---> System.Exception: Undantag av typen 'System.Exception' kastades.
där du kan se att den faktiska konstruktören bara körs en gång och undantaget används om igen.
Konstruktör och fastighetsinitialisering
Ska fastighetsvärdets uppdrag utföras före eller efter klassens konstruktör?
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
värdet vara 1
i klassens konstruktör eller efter TestProperty
i exemplet ovan?
Tilldela fastighetsvärden i instansskapningen så här:
var testInstance = new TestClass() {TestProperty = 1};
Kommer att köras efter att konstruktören har körts. Initiera emellertid fastighetsvärdet i klassens egendom i C # 6.0 så här:
public class TestClass
{
public int TestProperty { get; set; } = 2;
public TestClass()
{
}
}
kommer att göras innan konstruktören körs.
Kombinera de två koncepten ovan i ett enda exempel:
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
}
Slutresultat:
"Or shall this be executed"
"1"
Förklaring:
TestProperty
värdet tilldelas först som 2
, sedan TestClass
konstruktorn, vilket resulterar i utskrift av
"Or shall this be executed"
Och sedan kommer TestProperty
att tilldelas som 1
grund av new TestClass() { TestProperty = 1 }
, vilket gör det slutliga värdet för TestProperty
tryckt av Console.WriteLine(testInstance.TestProperty)
till
"1"