Как создать новый экземпляр объекта из Типа


747

Можно не всегда знать Typeобъект во время компиляции, но может потребоваться создать экземпляр объекта Type.

Как вы получаете новый экземпляр объекта от Type?

Ответы:


896

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

21
Рад, что, наконец, нашел это, но второй вызов не совсем правильно, пропуская кавычки и пароли обратные, должно быть: ObjectType instance = (ObjectType) Activator.CreateInstance ("MyAssembly", "MyNamespace.ObjectType");
Кевинк

10
Вам нужно вызвать 'Unwrap ()', чтобы получить фактический тип объекта, который вы хотите: ConcreteType instance = (ConcreteType) Activator.CreateInstance (objectType) .Unwrap ();
Ε Г И І И О

4
Как ObjectType instanceсоответствует условию OP «Не всегда может быть известен тип объекта во время компиляции»? : 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
Ларс Труидженс

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
Исключительное программирование? Это кажется очень плохой реализацией, когда вы можете просто размышлять над типом для определения конструкторов.
Firoso

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, OtherAssembly» своему первому методу, и он будет работать. Очевидно, что OtherAssembly - это название сборки, в которой он живет.
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

Без использования Reflection:

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

5
Чем это полезно? Вы уже должны знать тип для вызова этого метода, и если вы знаете тип, вы можете создать его без специального метода.
Кайл Делани

Так что T может меняться во время выполнения. Полезно, если вы работаете с производными типами.

новый T (); потерпит неудачу, если T не является ссылочным типом с конструктором без параметров. Этот метод использует ограничения, чтобы гарантировать, что T является ссылочным типом и имеет конструктор.

3
Как T может меняться во время выполнения? Разве вы не должны знать T во время разработки, чтобы вызвать Create <>?
Кайл Делани

Если вы работаете с универсальными классами и интерфейсами на фабриках, типы, которые реализуют интерфейс, должны быть созданы, могут отличаться.

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.