Buscar..


Introducción

Los constructores son métodos en una clase que se invocan cuando se crea una instancia de esa clase. Su principal responsabilidad es dejar el nuevo objeto en un estado útil y consistente.

Los destructores / finalizadores son métodos en una clase que se invocan cuando una instancia de eso se destruye. En C # rara vez se escriben / usan explícitamente.

Observaciones

C # en realidad no tiene destructores, sino finalizadores que usan la sintaxis del destructor de estilo C ++. Especificar un destructor anula el método Object.Finalize() que no se puede llamar directamente.

A diferencia de otros idiomas con una sintaxis similar, estos métodos no se llaman cuando los objetos están fuera del alcance, sino que se llaman cuando se ejecuta el recolector de basura, lo que ocurre bajo ciertas condiciones . Como tales, no están garantizados para funcionar en un orden particular.

Los finalizadores deben ser responsables de limpiar solo los recursos no administrados (los punteros adquiridos a través de la clase Marshal, recibidos a través de p / Invoke (llamadas al sistema) o los punteros sin procesar utilizados en bloques inseguros). Para limpiar los recursos administrados, revise IDisposable, el patrón de Disposición y la declaración de using .

(Lectura adicional: ¿ Cuándo debo crear un destructor? )

Constructor predeterminado

Cuando un tipo se define sin un constructor:

public class Animal
{
}

entonces el compilador genera un constructor predeterminado equivalente a lo siguiente:

public class Animal
{
    public Animal() {}
}

La definición de cualquier constructor para el tipo suprimirá la generación del constructor por defecto. Si el tipo se definiera de la siguiente manera:

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

entonces solo se puede crear un Animal llamando al constructor declarado.

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

Para el segundo ejemplo, el compilador mostrará un mensaje de error:

'Animal' no contiene un constructor que tome 0 argumentos

Si desea que una clase tenga un constructor sin parámetros y un constructor que toma un parámetro, puede hacerlo implementando explícitamente ambos constructores.

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

El compilador no podrá generar un constructor predeterminado si la clase extiende a otra clase que no tiene un constructor sin parámetros. Por ejemplo, si tuviéramos una clase Creature :

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

entonces Animal definido como class Animal : Creature {} no compilaría.

Llamando a un constructor desde otro constructor

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.

Constructor estático

Un constructor estático se llama la primera vez que se inicializa cualquier miembro de un tipo, se llama un miembro de clase estática o un método estático. El constructor estático es seguro para subprocesos. Un constructor estático se usa comúnmente para:

  • Inicialice el estado estático, es decir, el estado que se comparte en diferentes instancias de la misma clase.
  • Crear un singleton

Ejemplo:

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

Salida:

Animal inicializado
Animal creado
Animal creado

Ver demostración

Si la primera llamada es a un método estático, el constructor estático se invoca sin el constructor de instancia. Esto está bien, porque el método estático no puede acceder al estado de la instancia de todos modos.

Animal.Yawn();

Esto dará como resultado:

Animal inicializado
¡Bostezo!

Vea también Excepciones en constructores estáticos y Constructores estáticos genéricos .

Ejemplo de Singleton:

public class SessionManager
{
    public static SessionManager Instance;

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

Llamando al constructor de la clase base

Se llama a un constructor de una clase base antes de que se ejecute un constructor de una clase derivada. Por ejemplo, si Mammal extiende Animal , entonces el código contenido en el constructor de Animal se llama primero cuando se crea una instancia de un Mammal .

Si una clase derivada no especifica explícitamente a qué constructor de la clase base se debe llamar, el compilador asume el constructor sin parámetros.

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

En este caso, se imprimirá una instancia de un Mammal llamando al new Mammal("George the Cat")

Nace un animal desconocido.
George el gato es un mamífero.

Ver demostración

La llamada a un constructor diferente de la clase base se realiza colocando : base(args) entre la firma del constructor y su cuerpo:

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

Llamando new Mammal("George the Cat") ahora se imprimirá:

George el gato nace
George el gato es un mamífero.

Ver demostración

Finalizadores en clases derivadas.

Cuando se finaliza un gráfico de objetos, el orden es el inverso de la construcción. Por ejemplo, el supertipo se finaliza antes que el tipo base como lo demuestra el siguiente código:

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!

Patrón de constructor Singleton

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

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

Debido a que el constructor es privado, no se pueden crear nuevas instancias de SingletonClass consumiendo código. La única forma de acceder a la instancia única de SingletonClass es mediante el uso de la propiedad estática SingletonClass.Instance .

La propiedad de Instance es asignada por un constructor estático que genera el compilador de C #. El tiempo de ejecución de .NET garantiza que el constructor estático se ejecute como máximo una vez y se ejecute antes de que se lea la Instance primera vez. Por lo tanto, todas las preocupaciones de sincronización e inicialización se llevan a cabo por el tiempo de ejecución.

Tenga en cuenta que si el constructor estático falla, la clase Singleton quedará permanentemente inutilizable durante la vida útil del dominio de aplicación.

Además, no se garantiza que el constructor estático se ejecute en el momento del primer acceso de la Instance . Más bien, se ejecutará en algún momento antes de eso . Esto hace que el momento en el que se produce la inicialización no sea determinista. En casos prácticos, el JIT a menudo llama al constructor estático durante la compilación (no la ejecución) de un método que hace referencia a la Instance . Esta es una optimización de rendimiento.

Vea la página de Implementaciones de Singleton para otras formas de implementar el patrón de singleton.

Obligando a un constructor estático a ser llamado

Mientras que a los constructores estáticos siempre se les llama antes del primer uso de un tipo, a veces es útil poder forzarlos a ser llamados y la clase RuntimeHelpers proporciona un ayudante para ello:

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

Nota : se ejecutarán todas las inicializaciones estáticas (inicializadores de campos, por ejemplo), no solo el propio constructor.

Usos potenciales : forzar la inicialización durante la pantalla de inicio en una aplicación de interfaz de usuario o asegurar que un constructor estático no falle en una prueba de unidad.

Llamando a métodos virtuales en el constructor.

A diferencia de C ++ en C #, puede llamar a un método virtual desde el constructor de la clase (OK, también puede hacerlo en C ++, pero el comportamiento es sorprendente al principio). Por ejemplo:

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

Si viene de un fondo de C ++, esto es sorprendente, ¡el constructor de clases base ya ve la tabla de métodos virtuales de clases derivadas!

Tenga cuidado : la clase derivada aún no se ha inicializado completamente (su constructor se ejecutará después del constructor de la clase base) y esta técnica es peligrosa (también existe una advertencia de StyleCop para esto). Por lo general, esto es considerado como una mala práctica.

Constructores Estáticos Genéricos

Si el tipo en el que se declara el constructor estático es genérico, se llamará una vez al constructor estático para cada combinación única de argumentos genéricos.

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

    public static void Yawn() { }
}

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

Esto dará como resultado:

Sistema.Objeto
System.String

Vea también ¿Cómo funcionan los constructores estáticos para tipos genéricos?

Excepciones en constructores estáticos.

Si un constructor estático lanza una excepción, nunca se reintenta. El tipo no se puede utilizar durante la vida útil del dominio de aplicación. Cualquier uso posterior del tipo generará una TypeInitializationException alrededor de la excepción original.

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

Esto dará como resultado:

Ctor estático

System.TypeInitializationException: el inicializador de tipo para 'Animal' lanzó una excepción. ---> System.Exception: Excepción del tipo 'System.Exception' fue lanzada.

[...]

System.TypeInitializationException: el inicializador de tipo para 'Animal' lanzó una excepción. ---> System.Exception: Excepción del tipo 'System.Exception' fue lanzada.

donde puede ver que el constructor real solo se ejecuta una vez, y la excepción se reutiliza.

Constructor y inicialización de propiedades

¿Se debe ejecutar la asignación del valor de la propiedad antes o después del constructor de la clase?

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

En el ejemplo anterior, ¿será el valor TestProperty 1 en el constructor de la clase o después del constructor de la clase?


Asignando valores de propiedad en la creación de la instancia de esta manera:

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

Se ejecutará después de ejecutar el constructor. Sin embargo, al inicializar el valor de la propiedad en la propiedad class 'en C # 6.0 de esta forma:

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

    public TestClass() 
    {
    }
}

Se hará antes de que se ejecute el constructor.


Combinando los dos conceptos anteriores en un solo ejemplo:

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
}

Resultado final:

"Or shall this be executed"
"1"

Explicación:

El valor TestProperty primero se asignará como 2 , luego se TestClass constructor TestClass dará como resultado la impresión de

"Or shall this be executed"

Y luego, TestProperty se asignará como 1 debido a la new TestClass() { TestProperty = 1 } , lo que hace que el valor final de TestProperty impreso por Console.WriteLine(testInstance.TestProperty) sea

"1"


Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow