Як створити новий екземпляр об'єкта з типу


748

Не завжди можна знати Typeоб'єкт під час компіляції, але може знадобитися створити екземпляр Type.

Як отримати новий екземпляр об'єкта від Type?

Відповіді:


895

ActivatorКлас в кореневому Systemпросторі імен є досить потужним.

Існує велика кількість перевантажень для передачі параметрів конструктору тощо. Ознайомтеся з документацією за адресою:

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

або (новий шлях)

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

Ось кілька простих прикладів:

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

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

20
Радий, що нарешті знайшов це, але другий виклик не зовсім правильний, відсутня цитата та скасовано парми, повинні бути: ObjectType екземпляр = (ObjectType) Activator.CreateInstance ("MyAssembly", "MyNamespace.ObjectType");
kevinc

9
Вам потрібно зателефонувати "Unwrap ()", щоб отримати фактичний тип об'єкта, який ви хочете: ConcreteType instance = (ConcreteType) Activator.CreateInstance (objectType) .Unwrap ();
Ε Г І І І О

4
Як ObjectType instanceвідповідає умові ОП "Не завжди можна знати тип об'єкта під час компіляції"? : P
Мартін Шнайдер

@ MA-Maddin гаразд тоді , object instance = Activator.CreateInstance(...);.
BrainSlugs83

1
Хтось знає, як це зробити в .NET Core? Метод Unwrap для об'єкта недоступний.
Джастін

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

У Activatorкласі є загальний варіант, який робить це трохи простіше:

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

8
@Kevin Звичайно. Така операція не може працювати статично набраною мовою, оскільки це не має сенсу. Ви не можете викликати методи на об'єкті невідомого типу. У той же час (= так як пишу цей відповідь) C # отримала dynamicконструкцію , яка робить дозволяють такі конструкції , але для більшості цілей ця відповідь ще охоплює її.
Конрад Рудольф

1
@KonradRudolph Не зовсім правда. Перш C # це дозволяє створювати нові типи під час виконання. Ви просто не можете нічого зателефонувати на них статично безпечним способом . Так що так, ви наполовину правильні. Але більш реально це вам потрібно, коли ви завантажуєте збірки під час виконання, а це означає, що тип не відомий під час компіляції. C # був би сильно обмежений, якби ви не могли цього зробити. Я маю на увазі, що ви щойно це довели: як інакше працює метод Activator, який приймає екземпляр типу? Коли MS писала клас Activator, вони не мали знань у часі компіляції про будь-які майбутні типи користувачів.
AnorZaken

1
@AnorZaken Мій коментар нічого не говорить про створення типів під час виконання. Звичайно, ви можете це зробити, але ви не можете використовувати їх статично в одному контексті (ви можете розмістити повну статично складену програму, звичайно). Це все, про що йдеться в моєму коментарі.
Конрад Рудольф

@KonradRudolph Ах вибачте, неправильно трактували "таку операцію", щоб означати присвоєння типу, який відомий лише під час виконання; замість сенсу використовувати тип виконання як параметр загального типу.
AnorZaken

1
@AnorZaken - технічно ви можете одночасно створювати нові типи під час виконання І методи виклику на них статично безпечним способом, якщо ваш новий тип реалізує відомий інтерфейс або успадковує відомий базовий клас. - Будь-який із цих підходів дасть вам статичний контракт на ваш об’єкт, створений під час виконання.
BrainSlugs83

132

Складений вираз - найкращий спосіб! (для продуктивності для багаторазового створення екземпляра під час виконання).

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

X x = YCreator();

Статистика (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

Статистика (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

Статистика (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

Статистика (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

Статистика (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

Статистика (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

Повний код:

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 за всю статистику! На даний момент мені не потрібна така вистава, але все ще дуже цікава. :)
AnorZaken

1
Також є TypeDescriptor.CreateInstance (див stackoverflow.com/a/17797389/1242 ) , який може бути швидше , якщо використовується з TypeDescriptor.AddProvider
Ларс Truijens

2
Це все-таки корисно, коли ви не знаєте, який тип Xє під час виконання?
ajeh

1
@ajeh Так. Змініть typeof (T) на Type.GetType (..).
Serj-Tm

3
@ Serj-Tm Ні, це не працюватиме, якщо тип X - це час виконання Type.
NetMage

47

Однією з реалізацій цієї проблеми є спроба викликати конструктор без параметрів типу:

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

Ось такий же підхід, який міститься в загальному методі:

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

15
Програмування на основі винятків? Це здається дуже поганою реалізацією, коли ви можете просто задуматися над типом, щоб визначити конструктори.
Фіросо

16

Це досить просто. Припустимо, що ваше Carім'я класу є, а область імен - Vehicles, а потім передайте параметр, Vehicles.Carякий повертає об'єкт типу Car. Так, ви можете динамічно створювати будь-який примірник будь-якого класу.

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

Якщо ваше Повнокваліфіковане ім’я (тобто Vehicles.Carв даному випадку) знаходиться в іншій збірці, значення Type.GetTypeбуде недійсним. У таких випадках у вас є прохід через всі збори та знаходження Type. Для цього ви можете використовувати наведений нижче код

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

А ви можете отримати екземпляр, зателефонувавши до вищевказаного методу.

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

У вашому другому випадку (зовнішня збірка) ви можете просто перейти в "Vehicles.Car, OtherAssemble" до вашого першого методу, і він спрацює. Очевидно, що OtherAs асамблея - це назва асамблеї, на якій він живе.
danmiser

2
@danmiser Для цього потрібно жорстке кодування назви збірки. Для забезпечення гнучкості я перевіряю нуль і код працює в динамічному режимі :)
Сарат Аванаву

14

Якщо це для чогось, що буде називатися багато в екземплярі програми, набагато швидше збирати та кешувати динамічний код, а не використовувати активатор або ConstructorInfo.Invoke(). Два простих варіанти динамічної компіляції - компільовані Linq вирази або деякі прості ILкоди іDynamicMethod . У будь-якому випадку різниця величезна, коли ви починаєте потрапляти в тісні петлі або кілька дзвінків.



10

Якщо ви хочете скористатися конструктором за замовчуванням, то рішення, яке використовується System.Activatorпредставленим раніше, можливо, є найбільш зручним. Однак якщо типу не вистачає конструктора за замовчуванням або вам доведеться використовувати не за замовчуванням, тоді можливим є використання відображення або System.ComponentModel.TypeDescriptor. У випадку відображення досить знати лише назву типу (з його простором імен).

Приклад з використанням відображення:

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

Приклад з використанням 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[]саме це я прийшов до цього питання, дякую!
Чад

10

Без використання рефлексії:

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

5
Чим це корисно? Ви повинні знати тип вже для виклику цього методу, і якщо ви знаєте тип, ви можете його сконструювати без спеціального методу.
Кайл Делані

Тому T може змінюватись під час виконання. Корисно, якщо ви працюєте з виведеними типами.

новий Т (); не вдасться, якщо T не є посилальним типом з конструктором без параметрів. Цей метод використовує протипоказання, щоб переконатися, що T є еталонним типом і має конструктор.

3
Як T може змінюватись під час виконання? Вам не потрібно знати T під час проектування, щоб зателефонувати Створити <>?
Кайл Делані

Якщо ви працюєте з загальними класами та інтерфейсами на заводах, типи, які реалізують інтерфейс, повинен бути інстанційним, можуть відрізнятися.

8

Враховуючи цю проблему, Активатор буде працювати, коли є без параметрів ctor. Якщо це обмеження, розгляньте можливість використання

System.Runtime.Serialization.FormatterServices.GetSafeUninitializedObject()


4

Я можу відповісти на це питання, тому що я шукав реалізувати простий метод CloneObject для довільного класу (з конструктором за замовчуванням)

За допомогою загального методу ви можете вимагати, щоб тип реалізував 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

При невідомій припущенні тип має конструктор за замовчуванням і вилучає виняток, якщо цього немає.

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.