Cómo crear una nueva instancia de objeto a partir de un Tipo

747

Uno no siempre sabe el Type de un objeto en tiempo de compilación, pero es posible que deba crear una instancia de Type.

¿Cómo se obtiene una nueva instancia de objeto de a Type?

etiquetas2k
fuente

Respuestas:

896

La Activatorclase dentro del Systemespacio de nombres raíz es bastante poderosa.

Hay muchas sobrecargas para pasar parámetros al constructor y tal. Consulte la documentación en:

http://msdn.microsoft.com/en-us/library/system.activator.createinstance.aspx

o (nueva ruta)

https://docs.microsoft.com/en-us/dotnet/api/system.activator.createinstance

Aquí hay algunos ejemplos simples:

ObjectType instance = (ObjectType)Activator.CreateInstance(objectType);

ObjectType instance = (ObjectType)Activator.CreateInstance("MyAssembly","MyNamespace.ObjectType");
Karl Seguin
fuente
21
Me alegro de haber encontrado finalmente esto, pero la segunda llamada no es exactamente correcta, faltando una cita y parms invertidos, debería ser: ObjectType instance = (ObjectType) Activator.CreateInstance ("MyAssembly", "MyNamespace.ObjectType");
kevinc
10
Debe llamar a 'Unwrap ()' para obtener el tipo real de objeto que desea: ConcreteType instance = (ConcreteType) Activator.CreateInstance (objectType) .Unwrap ();
Ε Г И І И О
44
¿Cómo ObjectType instancecoincide la condición del OP "Uno no siempre sabe el tipo de un objeto en tiempo de compilación"? : P
Martin Schneider
@ MA-Maddin está bien, entonces object instance = Activator.CreateInstance(...);.
BrainSlugs83
1
¿Alguien sabe cómo hacer esto en .NET Core? El método Desenvolver no está disponible en el objeto.
Justin
145
ObjectType instance = (ObjectType)Activator.CreateInstance(objectType);

La Activatorclase tiene una variante genérica que hace esto un poco más fácil:

ObjectType instance = Activator.CreateInstance<ObjectType>();
Konrad Rudolph
fuente
8
@ Kevin Por supuesto. Tal operación no puede funcionar en un lenguaje tipado estáticamente porque no tiene sentido. No puede invocar métodos en un objeto de tipo desconocido. Mientras tanto (= desde escribir esta respuesta) C # tiene la dynamicconstrucción que hace permitir que tales construcciones, pero para la mayoría de los propósitos de esta respuesta todavía lo cubre.
Konrad Rudolph
1
@KonradRudolph No es del todo cierto. Primero de c # le permite crear nuevos tipos en tiempo de ejecución. Simplemente no se puede llamar a nada de una manera estáticamente segura . Entonces sí, estás medio correcto. Pero de manera más realista, necesita esto cuando carga ensamblajes en tiempo de ejecución, lo que significa que el tipo no se conoce en tiempo de compilación. C # estaría severamente limitado si no pudieras hacer esto. Quiero decir que lo probaste tú mismo: ¿de qué otra manera funciona el método Activador que toma una instancia de tipo? Cuando MS escribió la clase Activator, no tenían conocimiento en tiempo de compilación de los tipos futuros que escribirían los usuarios.
AnorZaken
1
@AnorZaken Mi comentario no dice nada sobre la creación de tipos en tiempo de ejecución. Por supuesto, puede hacerlo, pero no puede usarlos estáticamente en el mismo contexto (por supuesto, puede alojar un programa completo compilado estáticamente). Eso es todo lo que dice mi comentario.
Konrad Rudolph
@KonradRudolph Ah, lo siento, malinterpreté "tal operación" para significar instanciar un tipo que solo se conoce en tiempo de ejecución; en lugar de utilizar un tipo de tiempo de ejecución como parámetro de tipo genérico.
AnorZaken
1
@AnorZaken: técnicamente puede crear nuevos tipos en tiempo de ejecución Y llamar a métodos de manera estática segura si su nuevo tipo implementa una interfaz conocida o hereda una clase base conocida. - Cualquiera de esos enfoques le dará un contrato estático para su objeto creado en tiempo de ejecución.
BrainSlugs83
132

¡La expresión compilada es la mejor manera! (para que el rendimiento cree repetidamente una instancia en tiempo de ejecución).

static readonly Func<X> YCreator = Expression.Lambda<Func<X>>(
   Expression.New(typeof(Y).GetConstructor(Type.EmptyTypes))
 ).Compile();

X x = YCreator();

Estadísticas (2012):

    Iterations: 5000000
    00:00:00.8481762, Activator.CreateInstance(string, string)
    00:00:00.8416930, Activator.CreateInstance(type)
    00:00:06.6236752, ConstructorInfo.Invoke
    00:00:00.1776255, Compiled expression
    00:00:00.0462197, new

Estadísticas (2015, .net 4.5, x64):

    Iterations: 5000000
    00:00:00.2659981, Activator.CreateInstance(string, string)
    00:00:00.2603770, Activator.CreateInstance(type)
    00:00:00.7478936, ConstructorInfo.Invoke
    00:00:00.0700757, Compiled expression
    00:00:00.0286710, new

Estadísticas (2015, .net 4.5, x86):

    Iterations: 5000000
    00:00:00.3541501, Activator.CreateInstance(string, string)
    00:00:00.3686861, Activator.CreateInstance(type)
    00:00:00.9492354, ConstructorInfo.Invoke
    00:00:00.0719072, Compiled expression
    00:00:00.0229387, new

Estadísticas (2017, LINQPad 5.22.02 / x64 / .NET 4.6):

    Iterations: 5000000
    No args
    00:00:00.3897563, Activator.CreateInstance(string assemblyName, string typeName)
    00:00:00.3500748, Activator.CreateInstance(Type type)
    00:00:01.0100714, ConstructorInfo.Invoke
    00:00:00.1375767, Compiled expression
    00:00:00.1337920, Compiled expression (type)
    00:00:00.0593664, new
    Single arg
    00:00:03.9300630, Activator.CreateInstance(Type type)
    00:00:01.3881770, ConstructorInfo.Invoke
    00:00:00.1425534, Compiled expression
    00:00:00.0717409, new

Estadísticas (2019, x64 / .NET 4.8):

Iterations: 5000000
No args
00:00:00.3287835, Activator.CreateInstance(string assemblyName, string typeName)
00:00:00.3122015, Activator.CreateInstance(Type type)
00:00:00.8035712, ConstructorInfo.Invoke
00:00:00.0692854, Compiled expression
00:00:00.0662223, Compiled expression (type)
00:00:00.0337862, new
Single arg
00:00:03.8081959, Activator.CreateInstance(Type type)
00:00:01.2507642, ConstructorInfo.Invoke
00:00:00.0671756, Compiled expression
00:00:00.0301489, new

Estadísticas (2019, x64 / .NET Core 3.0):

Iterations: 5000000
No args
00:00:00.3226895, Activator.CreateInstance(string assemblyName, string typeName)
00:00:00.2786803, Activator.CreateInstance(Type type)
00:00:00.6183554, ConstructorInfo.Invoke
00:00:00.0483217, Compiled expression
00:00:00.0485119, Compiled expression (type)
00:00:00.0434534, new
Single arg
00:00:03.4389401, Activator.CreateInstance(Type type)
00:00:01.0803609, ConstructorInfo.Invoke
00:00:00.0554756, Compiled expression
00:00:00.0462232, new

Código completo:

static X CreateY_New()
{
    return new Y();
}

static X CreateY_New_Arg(int z)
{
    return new Y(z);
}

static X CreateY_CreateInstance()
{
    return (X)Activator.CreateInstance(typeof(Y));
}

static X CreateY_CreateInstance_String()
{
    return (X)Activator.CreateInstance("Program", "Y").Unwrap();
}

static X CreateY_CreateInstance_Arg(int z)
{
    return (X)Activator.CreateInstance(typeof(Y), new object[] { z, });
}

private static readonly System.Reflection.ConstructorInfo YConstructor =
    typeof(Y).GetConstructor(Type.EmptyTypes);
private static readonly object[] Empty = new object[] { };
static X CreateY_Invoke()
{
    return (X)YConstructor.Invoke(Empty);
}

private static readonly System.Reflection.ConstructorInfo YConstructor_Arg =
    typeof(Y).GetConstructor(new[] { typeof(int), });
static X CreateY_Invoke_Arg(int z)
{
    return (X)YConstructor_Arg.Invoke(new object[] { z, });
}

private static readonly Func<X> YCreator = Expression.Lambda<Func<X>>(
   Expression.New(typeof(Y).GetConstructor(Type.EmptyTypes))
).Compile();
static X CreateY_CompiledExpression()
{
    return YCreator();
}

private static readonly Func<X> YCreator_Type = Expression.Lambda<Func<X>>(
   Expression.New(typeof(Y))
).Compile();
static X CreateY_CompiledExpression_Type()
{
    return YCreator_Type();
}

private static readonly ParameterExpression YCreator_Arg_Param = Expression.Parameter(typeof(int), "z");
private static readonly Func<int, X> YCreator_Arg = Expression.Lambda<Func<int, X>>(
   Expression.New(typeof(Y).GetConstructor(new[] { typeof(int), }), new[] { YCreator_Arg_Param, }),
   YCreator_Arg_Param
).Compile();
static X CreateY_CompiledExpression_Arg(int z)
{
    return YCreator_Arg(z);
}

static void Main(string[] args)
{
    const int iterations = 5000000;

    Console.WriteLine("Iterations: {0}", iterations);

    Console.WriteLine("No args");
    foreach (var creatorInfo in new[]
    {
        new {Name = "Activator.CreateInstance(string assemblyName, string typeName)", Creator = (Func<X>)CreateY_CreateInstance},
        new {Name = "Activator.CreateInstance(Type type)", Creator = (Func<X>)CreateY_CreateInstance},
        new {Name = "ConstructorInfo.Invoke", Creator = (Func<X>)CreateY_Invoke},
        new {Name = "Compiled expression", Creator = (Func<X>)CreateY_CompiledExpression},
        new {Name = "Compiled expression (type)", Creator = (Func<X>)CreateY_CompiledExpression_Type},
        new {Name = "new", Creator = (Func<X>)CreateY_New},
    })
    {
        var creator = creatorInfo.Creator;

        var sum = 0;
        for (var i = 0; i < 1000; i++)
            sum += creator().Z;

        var stopwatch = new Stopwatch();
        stopwatch.Start();
        for (var i = 0; i < iterations; ++i)
        {
            var x = creator();
            sum += x.Z;
        }
        stopwatch.Stop();
        Console.WriteLine("{0}, {1}", stopwatch.Elapsed, creatorInfo.Name);
    }

    Console.WriteLine("Single arg");
    foreach (var creatorInfo in new[]
    {
        new {Name = "Activator.CreateInstance(Type type)", Creator = (Func<int, X>)CreateY_CreateInstance_Arg},
        new {Name = "ConstructorInfo.Invoke", Creator = (Func<int, X>)CreateY_Invoke_Arg},
        new {Name = "Compiled expression", Creator = (Func<int, X>)CreateY_CompiledExpression_Arg},
        new {Name = "new", Creator = (Func<int, X>)CreateY_New_Arg},
    })
    {
        var creator = creatorInfo.Creator;

        var sum = 0;
        for (var i = 0; i < 1000; i++)
            sum += creator(i).Z;

        var stopwatch = new Stopwatch();
        stopwatch.Start();
        for (var i = 0; i < iterations; ++i)
        {
            var x = creator(i);
            sum += x.Z;
        }
        stopwatch.Stop();
        Console.WriteLine("{0}, {1}", stopwatch.Elapsed, creatorInfo.Name);
    }
}

public class X
{
  public X() { }
  public X(int z) { this.Z = z; }
  public int Z;
}

public class Y : X
{
    public Y() {}
    public Y(int z) : base(z) {}
}
Serj-Tm
fuente
18
¡+1 para todas las estadísticas! Realmente no necesito este tipo de rendimiento en este momento, pero sigue siendo muy interesante. :)
AnorZaken 01 de
1
También hay TypeDescriptor.CreateInstance (ver stackoverflow.com/a/17797389/1242 ) que puede ser más rápido si se usa con TypeDescriptor.AddProvider
Lars Truijens el
2
¿Sigue siendo útil cuando no sabes qué tipo Xes en tiempo de ejecución?
ajeh
1
@ajeh Sí. Cambie typeof (T) a Type.GetType (..).
Serj-Tm
3
@ Serj-Tm No, eso no funcionará si el tipo X es un tiempo de ejecución Type.
NetMage
47

Una implementación de este problema es intentar llamar al constructor sin parámetros del Tipo:

public static object GetNewObject(Type t)
{
    try
    {
        return t.GetConstructor(new Type[] { }).Invoke(new object[] { });
    }
    catch
    {
        return null;
    }
}

Aquí está el mismo enfoque, contenido en un método genérico:

public static T GetNewObject<T>()
{
    try
    {
        return (T)typeof(T).GetConstructor(new Type[] { }).Invoke(new object[] { });
    }
    catch
    {
        return default(T);
    }
}
etiquetas2k
fuente
15
¿Programación impulsada por excepciones? Esto parece una implementación muy pobre cuando simplemente puede reflexionar sobre el tipo para determinar los constructores.
Firoso
16

Es muy simple. Suponga que su nombre de clase es Cary el espacio de nombres es Vehicles, luego pase el parámetro Vehicles.Carque devuelve el objeto de tipo Car. De esta manera, puede crear cualquier instancia de cualquier clase de forma dinámica.

public object GetInstance(string strNamesapace)
{         
     Type t = Type.GetType(strNamesapace); 
     return  Activator.CreateInstance(t);         
}

Si su nombre completo (es decir, Vehicles.Caren este caso) está en otro conjunto, Type.GetTypeserá nulo. En tales casos, debe recorrer todos los ensamblajes y buscar el Type. Para eso puedes usar el siguiente código

public object GetInstance(string strFullyQualifiedName)
{
     Type type = Type.GetType(strFullyQualifiedName);
     if (type != null)
         return Activator.CreateInstance(type);
     foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
     {
         type = asm.GetType(strFullyQualifiedName);
         if (type != null)
             return Activator.CreateInstance(type);
     }
     return null;
 }

Y puede obtener la instancia llamando al método anterior.

object objClassInstance = GetInstance("Vehicles.Car");
Sarath Avanavu
fuente
En su segundo caso (ensamblaje externo), puede pasar "Vehicles.Car, OtherAssembly" a su primer método y funcionará. Obviamente, OtherAssembly es el nombre de la asamblea en la que vive.
danmiser
2
@danmiser Eso necesita codificar el nombre del ensamblado. Para implementar la flexibilidad, estoy comprobando nulo y el código funciona de forma dinámica :)
Sarath Avanavu
14

Si se trata de algo que se llamará mucho en una instancia de aplicación, es mucho más rápido compilar y almacenar en caché el código dinámico en lugar de usar el activador o ConstructorInfo.Invoke(). Dos opciones fáciles para la compilación dinámica son las expresiones compiladas de Linq o algunos ILcódigos de operaciónDynamicMethod simples y . De cualquier manera, la diferencia es enorme cuando comienzas a entrar en ciclos cerrados o llamadas múltiples.

Tom Mayfield
fuente
11

¿No funcionaría el genérico T t = new T();?

Brady Moritz
fuente
99
En realidad, lo haría en una clase / método genérico, pero no para un "Tipo" dado.
Brady Moritz
Asume que el tipo T tiene la restricción 'new ()'.
Rob Von Nesselrode
10

Si desea utilizar el constructor predeterminado, entonces la solución que se System.Activatorpresenta anteriormente es probablemente la más conveniente. Sin embargo, si el tipo carece de un constructor predeterminado o si tiene que usar uno no predeterminado, entonces una opción es usar la reflexión o System.ComponentModel.TypeDescriptor. En caso de reflexión, es suficiente saber solo el nombre del tipo (con su espacio de nombres).

Ejemplo usando reflexión:

ObjectType instance = 
    (ObjectType)System.Reflection.Assembly.GetExecutingAssembly().CreateInstance(
        typeName: objectType.FulName, // string including namespace of the type
        ignoreCase: false,
        bindingAttr: BindingFlags.Default,
        binder: null,  // use default binder
        args: new object[] { args, to, constructor },
        culture: null, // use CultureInfo from current thread
        activationAttributes: null
    );

Ejemplo usando TypeDescriptor:

ObjectType instance = 
    (ObjectType)System.ComponentModel.TypeDescriptor.CreateInstance(
        provider: null, // use standard type description provider, which uses reflection
        objectType: objectType,
        argTypes: new Type[] { types, of, args },
        args: new object[] { args, to, constructor }
    );
BSharp
fuente
args[]fue exactamente lo que llegué a esta pregunta para encontrar, gracias!
Chad
10

Sin uso de Reflection:

private T Create<T>() where T : class, new()
{
    return new T();
}
meagar
fuente
55
¿Cómo es esto útil? Ya debe conocer el tipo para llamar a ese método, y si conoce el tipo, puede construirlo sin un método especial.
Kyle Delaney
Entonces T puede variar en tiempo de ejecución. Útil si trabaja con tipos derivados.
un nuevo T (); fallaría si T no es un tipo de referencia con un constructor sin parámetros, este método utiliza restricciones para garantizar que T sea un tipo de referencia y tenga un constructor.
3
¿Cómo puede variar T en tiempo de ejecución? ¿No tiene que saber T en tiempo de diseño para llamar a Crear <>?
Kyle Delaney
Si trabaja con clases e interfaces genéricas en fábricas, los tipos que implementan la interfaz deben ser instanciados pueden variar.
8

Ante este problema, el activador funcionará cuando haya un ctor sin parámetros. Si esto es una restricción, considere usar

System.Runtime.Serialization.FormatterServices.GetSafeUninitializedObject()
Thulani Chivandikwa
fuente
5
public AbstractType New
{
    get
    {
        return (AbstractType) Activator.CreateInstance(GetType());
    }
}
vikram nayak
fuente
4

Puedo superar esta pregunta porque estaba buscando implementar un método CloneObject simple para una clase arbitraria (con un constructor predeterminado)

Con el método genérico puede requerir que el tipo implemente New ().

Public Function CloneObject(Of T As New)(ByVal src As T) As T
    Dim result As T = Nothing
    Dim cloneable = TryCast(src, ICloneable)
    If cloneable IsNot Nothing Then
        result = cloneable.Clone()
    Else
        result = New T
        CopySimpleProperties(src, result, Nothing, "clone")
    End If
    Return result
End Function

Con no genérico, suponga que el tipo tiene un constructor predeterminado y captura una excepción si no lo tiene.

Public Function CloneObject(ByVal src As Object) As Object
    Dim result As Object = Nothing
    Dim cloneable As ICloneable
    Try
        cloneable = TryCast(src, ICloneable)
        If cloneable IsNot Nothing Then
            result = cloneable.Clone()
        Else
            result = Activator.CreateInstance(src.GetType())
            CopySimpleProperties(src, result, Nothing, "clone")
        End If
    Catch ex As Exception
        Trace.WriteLine("!!! CloneObject(): " & ex.Message)
    End Try
    Return result
End Function
Darrel Lee
fuente