Ricerca…


introduzione

I costruttori sono metodi in una classe che vengono invocati quando viene creata un'istanza di quella classe. La loro principale responsabilità è quella di lasciare il nuovo oggetto in uno stato utile e coerente.

I distruttori / finalizzatori sono metodi di una classe invocati quando un'istanza viene distrutta. In C # raramente vengono scritti / utilizzati esplicitamente.

Osservazioni

C # non ha effettivamente distruttori, ma piuttosto Finalizzatori che usano la sintassi del distruttore di stile C ++. La specifica di un distruttore sovrascrive il metodo Object.Finalize() che non può essere chiamato direttamente.

A differenza di altri linguaggi con sintassi simile, questi metodi non vengono chiamati quando gli oggetti escono dall'ambito, ma vengono chiamati quando viene eseguito il Garbage Collector, che si verifica in determinate condizioni . Come tali, essi non sono garantiti per l'esecuzione in un ordine particolare.

I finalizzatori dovrebbero essere responsabili della pulizia solo delle risorse non gestite (puntatori acquisiti tramite la classe Marshal, ricevuti tramite p / Invoke (chiamate di sistema) o puntatori grezzi utilizzati all'interno di blocchi non sicuri). Per ripulire le risorse gestite, rivedere IDisposable, il pattern Dispose e l'istruzione using .

(Ulteriori letture: quando dovrei creare un distruttore? )

Costruttore predefinito

Quando un tipo è definito senza un costruttore:

public class Animal
{
}

quindi il compilatore genera un costruttore predefinito equivalente al seguente:

public class Animal
{
    public Animal() {}
}

La definizione di qualsiasi costruttore per il tipo sopprimerà la generazione predefinita del costruttore. Se il tipo è stato definito come segue:

public class Animal
{
    public Animal(string name) {}
}

quindi un Animal può essere creato solo chiamando il costruttore dichiarato.

// This is valid
var myAnimal = new Animal("Fluffy");
// This fails to compile
var unnamedAnimal = new Animal();

Per il secondo esempio, il compilatore mostrerà un messaggio di errore:

'Animale' non contiene un costruttore che accetta 0 argomenti

Se si desidera che una classe abbia sia un costruttore senza parametri sia un costruttore che accetta un parametro, è possibile farlo implementando esplicitamente entrambi i costruttori.

public class Animal
{
    
    public Animal() {} //Equivalent to a default constructor.
    public Animal(string name) {}
}

Il compilatore non sarà in grado di generare un costruttore predefinito se la classe estende un'altra classe che non ha un costruttore senza parametri. Ad esempio, se avessimo una Creature classe:

public class Creature
{
    public Creature(Genus genus) {}
}

quindi Animal definito come class Animal : Creature {} non verrebbe compilato.

Chiamare un costruttore da un altro costruttore

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.

Costruttore statico

Un costruttore statico viene chiamato la prima volta che viene inizializzato un membro di un tipo, viene chiamato un membro di classe statico o un metodo statico. Il costruttore statico è thread-safe. Un costruttore statico è comunemente usato per:

  • Inizializza lo stato statico, ovvero lo stato che è condiviso tra diverse istanze della stessa classe.
  • Crea un singleton

Esempio:

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();

Produzione:

Animale inizializzato
Animale creato
Animale creato

Visualizza la demo

Se la prima chiamata riguarda un metodo statico, il costruttore statico viene richiamato senza il costruttore dell'istanza. Questo è OK, perché il metodo statico non può accedere allo stato di istanza in ogni caso.

Animal.Yawn();

Questo produrrà:

Animale inizializzato
Sbadiglio!

Vedi anche Eccezioni in costruttori statici e Generic Static Constructors .

Esempio Singleton:

public class SessionManager
{
    public static SessionManager Instance;

    static SessionManager()
    {
        Instance = new SessionManager();
    }
}

Chiamando il costruttore della classe base

Un costruttore di una classe base viene chiamato prima che venga eseguito un costruttore di una classe derivata. Ad esempio, se Mammal estende Animal , il codice contenuto nel costruttore di Animal viene chiamato prima quando si crea un'istanza di un Mammal .

Se una classe derivata non specifica esplicitamente quale costruttore della classe base deve essere chiamato, il compilatore assume il costruttore senza parametri.

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.");
    }
}

In questo caso, l'istanzializzazione di un Mammal chiamando il new Mammal("George the Cat") verrà stampata

Un animale sconosciuto nasce.
George the Cat è un mammifero.

Visualizza la demo

La chiamata a un diverso costruttore della classe base viene effettuata posizionando : base(args) tra la firma del costruttore e il suo corpo:

public class Mammal : Animal
{
    public Mammal(string name) : base(name)
    {
        Console.WriteLine(name + " is a mammal.");
    }
}

Chiamando il new Mammal("George the Cat") ora stamperà:

George the Cat nasce.
George the Cat è un mammifero.

Visualizza la demo

Finalizzatori su classi derivate

Quando un grafico dell'oggetto è finalizzato, l'ordine è il contrario della costruzione. Ad esempio, il super-tipo è finalizzato prima del tipo base come dimostra il seguente codice:

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!

Modello di costruttore Singleton

public class SingletonClass
{
    public static SingletonClass Instance { get; } = new SingletonClass();

    private SingletonClass()
    {
        // Put custom constructor code here
    }    
}

Poiché il costruttore è privato, non è possibile creare nuove istanze di SingletonClass mediante il consumo di codice. L'unico modo per accedere alla singola istanza di SingletonClass consiste nell'utilizzare la proprietà statica SingletonClass.Instance .

La proprietà Instance è assegnata da un costruttore statico generato dal compilatore C #. Il runtime .NET garantisce che il costruttore statico venga eseguito al massimo una volta e venga eseguito prima della prima lettura Instance . Pertanto, tutti i problemi di sincronizzazione e di inizializzazione vengono eseguiti dal runtime.

Nota che se il costruttore statico fallisce, la classe Singleton diventa definitivamente inutilizzabile per la vita di AppDomain.

Inoltre, non è garantito il funzionamento del costruttore statico al momento del primo accesso di Instance . Piuttosto, funzionerà prima o poi . Questo rende il momento in cui l'inizializzazione avviene non deterministica. Nei casi pratici, il JIT chiama spesso il costruttore statico durante la compilazione (non l'esecuzione) di un metodo che fa riferimento Instance . Questa è un'ottimizzazione delle prestazioni.

Vedere la pagina Implementazioni Singleton per altri modi per implementare il modello singleton.

Forzare un costruttore statico da chiamare

Sebbene i costruttori statici vengano sempre chiamati prima del primo utilizzo di un tipo, talvolta è utile poterli forzare a chiamare e la classe RuntimeHelpers fornisce un helper per questo:

using System.Runtime.CompilerServices;    
// ...
RuntimeHelpers.RunClassConstructor(typeof(Foo).TypeHandle);

Osservazione : verrà eseguita tutta l'inizializzazione statica (ad esempio gli inizializzatori dei campi), non solo il costruttore stesso.

Usi potenziali : forzatura dell'inizializzazione durante la schermata iniziale in un'applicazione di interfaccia utente o per garantire che un costruttore statico non fallisca in un test di unità.

Chiamare metodi virtuali nel costruttore

A differenza di C ++ in C # è possibile chiamare un metodo virtuale dal costruttore di classi (OK, è possibile anche in C ++ ma il comportamento all'inizio è sorprendente). Per esempio:

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

Se provieni da uno sfondo C ++ questo è sorprendente, il costruttore della classe base vede già la tabella del metodo virtuale della classe derivata!

Attenzione : la classe derivata potrebbe non essere stata ancora completamente inizializzata (il suo costruttore verrà eseguito dopo il costruttore della classe base) e questa tecnica è pericolosa (c'è anche un avvertimento StyleCop per questo). Di solito questo è considerato una cattiva pratica.

Generici costruttori statici

Se il tipo su cui è dichiarato il costruttore statico è generico, il costruttore statico verrà chiamato una volta per ogni combinazione univoca di argomenti generici.

class Animal<T>
{
    static Animal()
    {
        Console.WriteLine(typeof(T).FullName);
    }

    public static void Yawn() { }
}

Animal<Object>.Yawn();
Animal<String>.Yawn();

Questo produrrà:

System.Object
System.String

Vedi anche Come funzionano i costruttori statici per tipi generici?

Eccezioni nei costruttori statici

Se un costruttore statico genera un'eccezione, non viene mai ripetuto. Il tipo è inutilizzabile per la durata di AppDomain. Qualsiasi ulteriore utilizzo del tipo genererà una TypeInitializationException racchiusa TypeInitializationException originale.

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());
}

Questo produrrà:

Agente statico

System.TypeInitializationException: l'inizializzatore del tipo per 'Animal' ha generato un'eccezione. ---> System.Exception: è stata generata un'eccezione di tipo "System.Exception".

[...]

System.TypeInitializationException: l'inizializzatore del tipo per 'Animal' ha generato un'eccezione. ---> System.Exception: è stata generata un'eccezione di tipo "System.Exception".

dove puoi vedere che il costruttore vero e proprio viene eseguito una sola volta e l'eccezione viene riutilizzata.

Inizializzazione costruttore e proprietà

Il compito del valore della proprietà deve essere eseguito prima o dopo il costruttore della classe?

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 };

Nell'esempio sopra, il valore TestProperty deve essere 1 nel costruttore della classe o dopo il costruttore della classe?


Assegnazione dei valori delle proprietà nella creazione dell'istanza in questo modo:

var testInstance = new TestClass() {TestProperty = 1};

Verrà eseguito dopo l'esecuzione del costruttore. Tuttavia, inizializzando il valore della proprietà nella proprietà della classe in C # 6.0 in questo modo:

public class TestClass 
{
    public int TestProperty { get; set; } = 2;

    public TestClass() 
    {
    }
}

sarà fatto prima che venga eseguito il costruttore.


Combinando i due concetti sopra in un unico esempio:

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
}

Risultato finale:

"Or shall this be executed"
"1"

Spiegazione:

Il valore TestProperty verrà prima assegnato come 2 , quindi verrà eseguito il costruttore TestClass , con conseguente stampa di

"Or shall this be executed"

E quindi TestProperty verrà assegnato come 1 causa del new TestClass() { TestProperty = 1 } , rendendo il valore finale per TestProperty stampato da Console.WriteLine(testInstance.TestProperty) da

"1"


Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow