Llamar a un constructor desde otro

Tengo dos constructores que alimentan valores a campos de solo lectura.

public class Sample
{
    public Sample(string theIntAsString)
    {
        int i = int.Parse(theIntAsString);
        _intField = i;
    }

    public Sample(int theInt) => _intField = theInt;
    public int IntProperty    => _intField;

    private readonly int _intField;
}

Un constructor recibe los valores directamente y el otro hace algunos cálculos y obtiene los valores, luego establece los campos.

Ahora aquí está el truco:

  1. No quiero duplicar el código de configuración. En este caso, solo se establece un campo pero, por supuesto, puede haber más de uno.
  2. Para que los campos sean de solo lectura, necesito establecerlos desde el constructor, por lo que no puedo "extraer" el código compartido a una función de utilidad.
  3. No sé cómo llamar a un constructor de otro.

¿Alguna idea?

preguntado Oct 24 '10, 13:10

11 Respuestas

Me gusta:

public Sample(string str) : this(int.Parse(str)) { }

Respondido 20 ago 19, 19:08

@Avi: Puedes hacer un static método que manipula los parámetros. - SLaks

¿Puedo saber el orden de ejecución de éste? Todo en Sample(string) se ejecutará primero y luego Sample(int) o la versión int se ejecutará primero y luego volverá a la versión de cadena? (Como llamar super() en Java?) - Rosdi Kasim

@RosdiKasim: el constructor de la clase base siempre se ejecuta primero. No puedes usar o ver this hasta que se haya inicializado su clase base. - SLaks

@ivan_pozdeev: Sí, puedes; usar ?: o llamar a un método estático. - SLaks

@GeorgeBirbilis: Sí. Quería ejecutar código (en los parámetros) antes de llamar al otro ctor. En ese momento, no hay ninguna instancia. - SLaks

Si lo que desea no se puede lograr satisfactoriamente sin tener la inicialización en su propio método (por ejemplo, porque desea hacer demasiado antes del código de inicialización, o envolverlo en un intento, finalmente, o lo que sea), puede tener cualquiera o todos Los constructores pasan las variables de solo lectura por referencia a una rutina de inicialización, que luego podrá manipularlas a voluntad.

public class Sample
{
    private readonly int _intField;
    public int IntProperty => _intField; 

    private void setupStuff(ref int intField, int newValue) => intField = newValue;

    public Sample(string theIntAsString)
    {
        int i = int.Parse(theIntAsString);
        setupStuff(ref _intField,i);
    }

    public Sample(int theInt) => setupStuff(ref _intField, theInt);
}

Respondido 20 ago 19, 19:08

+1 solución real. Utilizando base(...) or this(...) solo podemos realizar operaciones muy limitadas. - shashwat

Absolutamente de acuerdo: la otra solución funciona, ¡pero no es la forma correcta de hacerlo (TM)! - Charleh

¿Qué tal usar el out palabra clave en lugar de ref? - Jeppe Stig Nielsen

@nawfal: Porque no puede hacerlo si la variable es readonly. - Super gato

@JohnCarpenter: Si solo uno readonly campo debe establecerse, el código que lo establece podría llamar al método y asignar el campo utilizando el valor de retorno, pero cualquier número de campos se puede escribir directamente con ref. Además, en caso de que importe, los cambios realizados a través del ref Los parámetros tienen lugar inmediatamente, incluso antes de que la función regrese, mientras que los que se hicieron usando un valor de retorno de función no lo harían. - Super gato

Antes del cuerpo del constructor, use:

: base (parameters)

: this (parameters)

Ejemplo:

public class People: User
{
   public People (int EmpID) : base (EmpID)
   {
      // Add more statements here.
   }
}

Respondido el 15 de enero de 16 a las 23:01

Desafortunadamente, no funciona si necesito algunas operaciones en argumentos entre llamadas de constructores. - Denis

@Denis ¿No puedes encadenar un constructor en el medio para lograr el mismo efecto? - danim

@Denis no puede hacer nada antes de llamar a un constructor. Si desea hacer algo antes de inicializar las propiedades de un objeto, mueva la inicialización en un método que no sea el ejemplo del constructor init(). Puede llamar a este método desde cualquiera de sus constructores. - Abdalá Shoaib

@AbdullahShoaib no cuando necesita llamar a un constructor padre. - conejera moore

Estoy mejorando la respuesta de supercat. Supongo que también se puede hacer lo siguiente:

class Sample
{
    private readonly int _intField;
    public int IntProperty
    {
        get { return _intField; }
    }

    void setupStuff(ref int intField, int newValue)
    {
        //Do some stuff here based upon the necessary initialized variables.
        intField = newValue;
    }

    public Sample(string theIntAsString, bool? doStuff = true)
    {
        //Initialization of some necessary variables.
        //==========================================
        int i = int.Parse(theIntAsString);
        // ................
        // .......................
        //==========================================

        if (!doStuff.HasValue || doStuff.Value == true)
           setupStuff(ref _intField,i);
    }

    public Sample(int theInt): this(theInt, false) //"false" param to avoid setupStuff() being called two times
    {
        setupStuff(ref _intField, theInt);
    }
}

respondido 28 nov., 12:13

Esto posiblemente permitiría a un tercero crear una muestra sin configurarla, llamando new Sample(str, false). - Teejay

Esto no se compila. - kayleefrye_ondeck

Este no es un buen enfoque; confuso; innecesariamente complicado. Si llamas a otro constructor usando this, luego deja que ese constructor llame setupStuff; elimine la llamada a setupStuff en el último constructor. Entonces no necesitas el doStuff / false parámetro. (Una queja menor es que si tiene una razón para usar doStuff parámetro, no hay ningún beneficio en hacer que un valor booleano anulable bool?. Solo usa bool.) Además, lo que Teejay señaló, significa que este es un diseño fatalmente defectuoso. - Fabricante de herramientasSteve

Un mejor código podría ser: public Sample(string theIntAsString) : this(int.Parse(theIntAsString)) {} public Sample(int theInt) { setupStuff(ref _intField, theInt); } Tenga en cuenta que el primer constructor, que llama a otro constructor, no llamar al setupStuff. - Fabricante de herramientasSteve

Aquí hay un ejemplo que llama a otro constructor y luego verifica la propiedad que ha establecido.

    public SomeClass(int i)
    {
        I = i;
    }

    public SomeClass(SomeOtherClass soc)
        : this(soc.J)
    {
        if (I==0)
        {
            I = DoSomethingHere();
        }
    }

contestado el 23 de mayo de 14 a las 01:05

Esto es potencialmente mucho más limpio si está utilizando el constructor predeterminado en algunos casos y haciendo cambios pequeños / específicos para otros. - Adán Tolley

¡Sí, puedes llamar a otro método antes de la base de llamadas o esto!

public class MyException : Exception
{
    public MyException(int number) : base(ConvertToString(number)) 
    {
    }

    private static string ConvertToString(int number) 
    { 
      return number.toString()
    }

}

Respondido el 07 de junio de 16 a las 21:06

Solo por el bien de la respuesta general: si su constructor debe inicializar cualquier campo de solo lectura, no puede usar métodos para esto. - lentinante

Cuando hereda una clase de una clase base, puede invocar el constructor de la clase base creando una instancia de la clase derivada

class sample
{
    public int x;

    public sample(int value)
    {
        x = value;
    }
}

class der : sample
{
    public int a;
    public int b;

    public der(int value1,int value2) : base(50)
    {
        a = value1;
        b = value2;
    }
}

class run 
{
    public static void Main(string[] args)
    {
        der obj = new der(10,20);

        System.Console.WriteLine(obj.x);
        System.Console.WriteLine(obj.a);
        System.Console.WriteLine(obj.b);
    }
}

Salida del programa de muestra is

50 10 20


También puedes usar this palabra clave para invocar un constructor de otro constructor

class sample
{
    public int x;

    public sample(int value) 
    {
        x = value;
    }

    public sample(sample obj) : this(obj.x) 
    {
    }
}

class run
{
    public static void Main(string[] args) 
    {
        sample s = new sample(20);
        sample ss = new sample(s);

        System.Console.WriteLine(ss.x);
    }
}

La salida de este programa de muestra is

20

contestado el 03 de mayo de 18 a las 05:05

Encadenamiento de constructores es decir, puede usar "Base" para Es una relación y "Esto" puede usar para la misma clase, cuando desee llamar a varios Constructores en una sola llamada.

  class BaseClass
{
    public BaseClass():this(10)
    {
    }
    public BaseClass(int val)
    {
    }
}
    class Program
    {
        static void Main(string[] args)
        {
            new BaseClass();
            ReadLine();
        }
    }

Respondido 10 Oct 18, 15:10

El manejo de errores y hacer que su código sea reutilizable es clave. Agregué una cadena a la validación int y es posible agregar otros tipos si es necesario. Resolver este problema con una solución más reutilizable podría ser la siguiente:

public class Sample
{
    public Sample(object inputToInt)
    {
        _intField = objectToInt(inputToInt);
    }

    public int IntProperty => _intField;

    private readonly int _intField;
}

public static int objectToInt(object inputToInt)
{
    switch (inputToInt)
        {
            case int inputInt:
                return inputInt;
            break;
            case string inputString:
            if (!int.TryParse(inputString, out int parsedInt))
            {
                throw new InvalidParameterException($"The input {inputString} could not be parsed to int");
            }
            return parsedInt;

            default:
                throw new InvalidParameterException($"Constructor do not support {inputToInt.GetType().Name}");
            break;
        }
}

Respondido el 23 de enero de 20 a las 10:01

En caso de que necesite ejecutar algo antes de llamar a otro constructor, no después.

public class Sample
{
    static int preprocess(string theIntAsString)
    {
        return preprocess(int.Parse(theIntAsString));
    }

    static int preprocess(int theIntNeedRounding)
    {
        return theIntNeedRounding/100;
    }

    public Sample(string theIntAsString)
    {
        _intField = preprocess(theIntAsString)
    }

    public Sample(int theIntNeedRounding)
    {
        _intField = preprocess(theIntNeedRounding)
    }

    public int IntProperty  => _intField;

    private readonly int _intField;
}

Y ValueTuple puede ser muy útil si necesita configurar más de un campo.

Respondido el 27 de diciembre de 19 a las 08:12

Por favor, por favor, y por favor, no intentes esto en casa, ni en el trabajo, ni en ningún otro lugar.

Esta es una forma de resolver un problema muy muy específico, y espero que no lo tenga.

Estoy publicando esto ya que técnicamente es una respuesta y otra perspectiva para verla.

Repito, no lo uses bajo ninguna condición. El código debe ejecutarse con LINQPad.

void Main()
{
    (new A(1)).Dump();
    (new B(2, -1)).Dump();
    
    var b2 = new B(2, -1);
    b2.Increment();
    
    b2.Dump();
}

class A 
{
    public readonly int I = 0;
    
    public A(int i)
    {
        I = i;
    }
}

class B: A
{
    public int J;
    public B(int i, int j): base(i)
    {
        J = j;
    }
    
    public B(int i, bool wtf): base(i)
    {
    }
    
    public void Increment()
    {
        int i = I + 1;

        var t = typeof(B).BaseType;
        var ctor = t.GetConstructors().First();
        
        ctor.Invoke(this, new object[] { i });
    }
}

Dado que el constructor es un método, puede llamarlo con reflexión. Ahora piensa con portales o visualiza una imagen de una lata de gusanos. perdón por esto.

Respondido el 22 de Septiembre de 20 a las 22:09

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.