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

Visa demo

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.

Visa demo

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.

Visa demo

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"


Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow