Crear instancia de tipo sin constructor predeterminado en C # usando reflexión

97

Tome la siguiente clase como ejemplo:

class Sometype
{
    int someValue;

    public Sometype(int someValue)
    {
        this.someValue = someValue;
    }
}

Luego quiero crear una instancia de este tipo usando la reflexión:

Type t = typeof(Sometype);
object o = Activator.CreateInstance(t);

Normalmente esto funcionará, sin embargo, debido a que SomeTypeno se ha definido un constructor sin parámetros, la llamada a Activator.CreateInstancearrojará una excepción de tipo MissingMethodExceptioncon el mensaje " No se ha definido ningún constructor sin parámetros para este objeto " . ¿Existe alguna forma alternativa de crear una instancia de este tipo? Sería un poco asqueroso agregar constructores sin parámetros a todas mis clases.

Aistina
fuente
2
FormatterServices.GetUninitializedObjectno permite crear una cadena no inicializada. Es posible que reciba una excepción: System.ArgumentException: Uninitialized Strings cannot be created.tenga esto en cuenta.
Bartosz Pierzchlewicz
Gracias por el aviso, pero ya estoy manejando cadenas y tipos básicos por separado.
Aistina

Respuestas:

142

Originalmente publiqué esta respuesta aquí , pero aquí hay una reimpresión ya que esta no es exactamente la misma pregunta pero tiene la misma respuesta:

FormatterServices.GetUninitializedObject()creará una instancia sin llamar a un constructor. Encontré esta clase usando Reflector y explorando algunas de las clases principales de serialización de .Net.

Lo probé usando el código de muestra a continuación y parece que funciona muy bien:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Runtime.Serialization;

namespace NoConstructorThingy
{
    class Program
    {
        static void Main(string[] args)
        {
            MyClass myClass = (MyClass)FormatterServices.GetUninitializedObject(typeof(MyClass)); //does not call ctor
            myClass.One = 1;
            Console.WriteLine(myClass.One); //write "1"
            Console.ReadKey();
        }
    }

    public class MyClass
    {
        public MyClass()
        {
            Console.WriteLine("MyClass ctor called.");
        }

        public int One
        {
            get;
            set;
        }
    }
}
Jason Jackson
fuente
Impresionante, parece que eso es exactamente lo que necesito. ¿Supongo que no inicializado significa que toda su memoria se establecerá en ceros? (Similar a cómo se instancian las estructuras)
Aistina
Cualquiera que sea el valor predeterminado para cada tipo será el predeterminado. Entonces los objetos serán nulos, ints 0, etc. Creo que ocurre cualquier inicialización a nivel de clase, pero no se ejecuta ningún constructor.
Jason Jackson
14
@JSBangs, eso apesta, estás dando una respuesta perfectamente legítima. Su comentario y la otra respuesta no abordan realmente la pregunta formulada. Si cree que tiene una mejor respuesta, proporcione una. Pero la respuesta que proporcioné resalta cómo usar una clase documentada de la misma manera que otras clases de serialización usan este código.
Jason Jackson
21
@JSBangs FormatterServices ( msdn.microsoft.com/en-us/library/… ) no está indocumentado.
Autodidacta
72

Utilice esta sobrecarga del método CreateInstance:

public static Object CreateInstance(
    Type type,
    params Object[] args
)

Crea una instancia del tipo especificado utilizando el constructor que mejor coincide con los parámetros especificados.

Ver: http://msdn.microsoft.com/en-us/library/wcxyzt4d.aspx

Mella
fuente
1
Esta solución simplifica demasiado el problema. ¿Qué sucede si no conozco mi tipo y digo "simplemente cree un objeto de la variable Tipo en este tipo"?
kamii
23

Cuando comparé el rendimiento (T)FormatterServices.GetUninitializedObject(typeof(T)), fue más lento. Al mismo tiempo, las expresiones compiladas le brindarían grandes mejoras de velocidad, aunque solo funcionan para tipos con constructor predeterminado. Adopté un enfoque híbrido:

public static class New<T>
{
    public static readonly Func<T> Instance = Creator();

    static Func<T> Creator()
    {
        Type t = typeof(T);
        if (t == typeof(string))
            return Expression.Lambda<Func<T>>(Expression.Constant(string.Empty)).Compile();

        if (t.HasDefaultConstructor())
            return Expression.Lambda<Func<T>>(Expression.New(t)).Compile();

        return () => (T)FormatterServices.GetUninitializedObject(t);
    }
}

public static bool HasDefaultConstructor(this Type t)
{
    return t.IsValueType || t.GetConstructor(Type.EmptyTypes) != null;
}

Esto significa que la expresión de creación se almacena en caché de manera efectiva y solo se penaliza la primera vez que se carga el tipo. Manejará los tipos de valor también de manera eficiente.

Llámalo:

MyType me = New<MyType>.Instance();

Tenga en cuenta que (T)FormatterServices.GetUninitializedObject(t)fallará para la cadena. Por lo tanto, existe un manejo especial para la cadena para devolver una cadena vacía.

nawfal
fuente
1
Es extraño cómo una mirada a una línea del código de alguien puede salvar un día. ¡Gracias Señor! Las razones de rendimiento me llevaron a su publicación y el truco está hecho :) Las clases FormatterServices y Activator tienen un rendimiento inferior en comparación con las expresiones compiladas, qué lástima que se encuentren Activadores por todas partes.
jmodrak
@nawfal Con respecto a su manejo especial para cadenas, sé que fallaría para cadenas sin este manejo especial, pero solo quiero saber: ¿funcionará para todos los demás tipos?
Sнаđошƒаӽ
@ Sнаđошƒаӽ desafortunadamente no. El ejemplo dado es barebones y .NET tiene muchos tipos diferentes de tipos. Por ejemplo, considere, si pasa un tipo de delegado, ¿cómo le dará una instancia? O de lo contrario, si el constructor arroja, ¿qué puede hacer al respecto? Muchas formas diferentes de manejarlo. Desde que respondí esto, se actualizó para manejar muchos más escenarios en mi biblioteca. No está publicado en ninguna parte por ahora.
nawfal
4

Buenas respuestas pero inutilizables en el marco compacto de dot net. Aquí hay una solución que funcionará en CF.Net ...

class Test
{
    int _myInt;

    public Test(int myInt)
    {
        _myInt = myInt;
    }

    public override string ToString()
    {
        return "My int = " + _myInt.ToString();
    }
}

class Program
{
    static void Main(string[] args)
    {
        var ctor = typeof(Test).GetConstructor(new Type[] { typeof(int) });
        var obj = ctor.Invoke(new object[] { 10 });
        Console.WriteLine(obj);
    }
}
Autodidacta
fuente
1
Esta es la forma en que llamaría a un constructor no predeterminado. No estoy seguro de querer crear un objeto sin llamar a ningún constructor.
Rory MacLeod
2
Es posible que desee crear un objeto sin llamar a constructores si está escribiendo serializadores personalizados.
Autodidacta
1
Sí, ese es el escenario de caso de uso exacto para el que fue esta pregunta :)
Aistina
1
@Aistina ¿Quizás podría agregar esta información a la pregunta? La mayoría de la gente estaría en contra de crear objetos sin llamar a sus autores y se tomaría el tiempo para discutir con usted sobre eso, pero su caso de uso realmente lo justifica, así que creo que es muy relevante para la pregunta en sí.
julealgon