Jak utworzyć nową instancję obiektu na podstawie typu


748

Nie zawsze można znać Typeobiekt w czasie kompilacji, ale może być konieczne utworzenie instancji obiektu Type.

Jak uzyskać nową instancję obiektu z Type?

Odpowiedzi:


895

ActivatorKlasa obrębie głównego Systemobszaru nazw jest dość silny.

Istnieje wiele przeciążeń związanych z przekazywaniem parametrów do konstruktora i tym podobnych. Sprawdź dokumentację na:

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

lub (nowa ścieżka)

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

Oto kilka prostych przykładów:

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

ObjectType instance = (ObjectType)Activator.CreateInstance("MyAssembly","MyNamespace.ObjectType");

20
Cieszę się, że w końcu to znalazłem, ale drugie wywołanie nie jest dokładnie poprawne, brak cytatu i odwrócenie parms powinny być następujące: ObjectType instance = (ObjectType) Activator.CreateInstance („MyAssembly”, „MyNamespace.ObjectType”);
kevinc

9
Musisz wywołać „Unwrap ()”, aby uzyskać rzeczywisty typ pożądanego obiektu: ConcreteType instance = (ConcreteType) Activator.CreateInstance (objectType) .Unwrap ();
Ε Г И І И О

4
W jaki sposób ObjectType instancespełnia warunek PO „Nie zawsze można poznać typ obiektu w czasie kompilacji”? : P
Martin Schneider

@ MA-Maddin w porządku, więc object instance = Activator.CreateInstance(...);.
BrainSlugs83

1
Czy ktoś wie jak to zrobić w .NET Core? Metoda Unwrap nie jest dostępna na obiekcie.
Justin

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

ActivatorKlasa ma ogólny wariant, który sprawia, że nieco łatwiej:

ObjectType instance = Activator.CreateInstance<ObjectType>();

8
@Kevin Oczywiście. Taka operacja nie może działać w języku o typie statycznym, ponieważ nie ma sensu. Nie można wywoływać metod na obiekcie nieznanego typu. W międzyczasie (= od pisania tego odpowiedź) C # got dynamickonstrukt, który robi umożliwić takie konstrukty, ale w większości przypadków ta odpowiedź nadal obejmuje ją.
Konrad Rudolph

1
@KonradRudolph Niezupełnie prawda. Przede c # nie pozwalają na tworzenie nowych typów w czasie wykonywania. Po prostu nie możesz do nich zadzwonić w statystycznie bezpieczny sposób . Więc tak, jesteś w połowie poprawny. Ale bardziej realistycznie potrzebujesz tego podczas ładowania zestawów w czasie wykonywania, co oznacza, że ​​typ nie jest znany w czasie kompilacji. C # byłby poważnie ograniczony, gdybyś nie mógł tego zrobić. Mam na myśli, że sam to udowodniłeś: jak inaczej działa metoda Activatora, która wymaga instancji typu? Kiedy MS napisało klasę Activator, nie mieli żadnej wiedzy na temat jakichkolwiek przyszłych typów, które pisaliby użytkownicy.
AnorZaken

1
@AnorZaken Mój komentarz nic nie mówi o tworzeniu typów w czasie wykonywania. Oczywiście możesz to zrobić, ale nie możesz używać ich statycznie w tym samym kontekście (możesz oczywiście hostować w pełni skompilowany program statyczny). To wszystko, co mówi mój komentarz.
Konrad Rudolph

@KonradRudolph Ach, przepraszam, źle zinterpretowałem „taką operację”, oznaczając utworzenie typu, który jest znany tylko w czasie wykonywania; zamiast używać typu środowiska wykonawczego jako parametru typu ogólnego.
AnorZaken

1
@AnorZaken - technicznie można zarówno tworzyć nowe typy w czasie wykonywania ORAZ wywoływać na nich metody w sposób statycznie bezpieczny, jeśli nowy typ implementuje znany interfejs lub dziedziczy znaną klasę podstawową. - Każde z tych podejść da ci statyczny kontrakt na tworzony obiekt wykonawczy.
BrainSlugs83

132

Skompilowane wyrażenie jest najlepszym sposobem! (dla wydajności, aby wielokrotnie tworzyć instancję w środowisku wykonawczym).

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

X x = YCreator();

Statystyki (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

Statystyki (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

Statystyki (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

Statystyki (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

Statystyki (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

Statystyki (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

Pełny kod:

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) {}
}

18
+1 za wszystkie statystyki! Tak naprawdę nie potrzebuję tego rodzaju występów, ale wciąż bardzo interesujących. :)
AnorZaken

1
Istnieje również TypeDescriptor.CreateInstance (patrz stackoverflow.com/a/17797389/1242 ), który może być szybszy, jeśli jest używany z TypeDescriptor.AddProvider
Lars Truijens

2
Czy jest to nadal przydatne, gdy nie wiesz, jaki typ Xjest w czasie wykonywania?
ajeh

1
@ajeh Tak. Zmień typeof (T) na Type.GetType (..).
Serj-Tm

3
@ Serj-Tm Nie, to nie zadziała, jeśli typ X to środowisko wykonawcze Type.
NetMage,

47

Jedną z realizacji tego problemu jest próba wywołania konstruktora bez parametrów typu:

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

Oto to samo podejście zawarte w ogólnej metodzie:

public static T GetNewObject<T>()
{
    try
    {
        return (T)typeof(T).GetConstructor(new Type[] { }).Invoke(new object[] { });
    }
    catch
    {
        return default(T);
    }
}

15
Programowanie oparte na wyjątkach? Wydaje się to bardzo słabą implementacją, gdy można po prostu zastanowić się nad typem, aby określić konstruktory.
Firoso

16

To całkiem proste. Załóżmy, że twoja nazwa klasy to Cari przestrzeń nazw to Vehicles, a następnie przekaż parametr, Vehicles.Carktóry zwraca obiekt typu Car. W ten sposób możesz dynamicznie tworzyć dowolne wystąpienia dowolnej klasy.

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

Jeśli twoje w pełni kwalifikowane imię (tj. Vehicles.CarW tym przypadku) znajduje się w innym zbiorze, Type.GetTypebędzie puste. W takich przypadkach przeglądasz wszystkie zespoły i odnajdujesz Type. W tym celu możesz użyć poniższego kodu

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

I możesz uzyskać instancję, wywołując powyższą metodę.

object objClassInstance = GetInstance("Vehicles.Car");

W drugim przypadku (montaż zewnętrzny) możesz po prostu przekazać „Vehicles.Car, OtherAssembly” do pierwszej metody i zadziała. Oczywiście OtherAssembly to nazwa zgromadzenia, w którym żyje.
danmiser

2
@danmiser To wymaga twardego kodowania nazwy zestawu. W celu wdrożenia elastyczności sprawdzam null, a kod działa w sposób dynamiczny :)
Sarath Avanavu

14

Jeśli dotyczy to czegoś, co będzie często nazywane w instancji aplikacji, kompilowanie i buforowanie dynamicznego kodu jest znacznie szybsze niż użycie aktywatora lub ConstructorInfo.Invoke(). Dwie proste opcje dynamicznej kompilacji to skompilowane wyrażenia Linq lub kilka prostych ILkodów operacyjnych iDynamicMethod . Tak czy inaczej, różnica jest ogromna, gdy zaczynasz wchodzić w ciasne pętle lub wiele połączeń.



10

Jeśli chcesz użyć domyślnego konstruktora, to rozwiązanie wykorzystujące System.Activatorprzedstawione wcześniej jest prawdopodobnie najwygodniejsze. Jeśli jednak typowi brakuje domyślnego konstruktora lub musisz użyć innego niż domyślny konstruktora, wówczas można użyć odbicia lub System.ComponentModel.TypeDescriptor. W przypadku refleksji wystarczy znać tylko nazwę typu (wraz z przestrzenią nazw).

Przykład użycia odbicia:

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

Przykład użycia 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 }
    );

args[]było dokładnie to, co znalazłem na to pytanie, dzięki!
Czad

10

Bez użycia Refleksji:

private T Create<T>() where T : class, new()
{
    return new T();
}

5
Jak to jest przydatne? Musisz znać typ, aby wywołać tę metodę, a jeśli znasz typ, możesz go zbudować bez specjalnej metody.
Kyle Delaney

Tak więc T może się różnić w czasie wykonywania. Przydatne, jeśli pracujesz z wytypowanymi typami.

nowa T (); nie powiedzie się, jeśli T nie jest typem odniesienia z konstruktorem bez parametrów, Ta metoda wykorzystuje ograniczenia, aby upewnić się, że T jest typem odniesienia i ma konstruktor.

3
Jak T może się różnić w czasie wykonywania? Nie musisz znać T w czasie projektowania, aby zadzwonić do Utwórz <>?
Kyle Delaney

Jeśli pracujesz z klasami ogólnymi i interfejsami w fabrykach, typy, które implementują interfejs powinny być ustawione, mogą się różnić.

8

Biorąc pod uwagę ten problem, Aktywator będzie działał, gdy pojawi się parametryczny ctor. Jeśli jest to ograniczenie, rozważ użycie

System.Runtime.Serialization.FormatterServices.GetSafeUninitializedObject()

5
public AbstractType New
{
    get
    {
        return (AbstractType) Activator.CreateInstance(GetType());
    }
}

4

Mogę przejść przez to pytanie, ponieważ chciałem zaimplementować prostą metodę CloneObject dla dowolnej klasy (z domyślnym konstruktorem)

Metodą ogólną można wymagać, aby typ implementował metodę 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

Zakładając, że nie jest to rodzaj, załóż, że typ ma domyślnego konstruktora, a jeśli nie, złap wyjątek.

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.