Не всегда можно знать Type объекта во время компиляции, но может потребоваться создать экземпляр Type.
Как получить новый экземпляр объекта от Type?





Класс 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");
Вам нужно вызвать Unwrap (), чтобы получить фактический тип объекта, который вы хотите: ConcreteType instance = (ConcreteType) Activator.CreateInstance (objectType) .Unwrap ();
Как ObjectType instance соответствует условию OP «Не всегда можно знать тип объекта во время компиляции»? :П
@ МА-Маддин, ладно, object instance = Activator.CreateInstance(...);.
Кто-нибудь знает, как это сделать в .NET Core? Метод Unwrap недоступен для объекта.
Если это для чего-то, что будет часто вызываться в экземпляре приложения, гораздо быстрее компилировать и кэшировать динамический код вместо использования активатора или ConstructorInfo.Invoke(). Два простых варианта динамической компиляции - это скомпилированный Выражения Linq или какой-нибудь простой Коды операций IL и DynamicMethod. В любом случае разница огромна, когда вы начинаете попадать в узкие циклы или множественные вызовы.
ObjectType instance = (ObjectType)Activator.CreateInstance(objectType);
Класс Activator имеет общий вариант, который немного упрощает задачу:
ObjectType instance = Activator.CreateInstance<ObjectType>();
@Kevin Конечно. Такая операция не могу работает на статически типизированном языке, потому что она не имеет смысла. Вы не можете вызывать методы для объекта неизвестного типа. Между тем (= с момента написания этого ответа) C# имеет конструкцию dynamic, которая делает допускает такие конструкции, но для большинства целей этот ответ все еще покрывает ее.
@KonradRudolph Не совсем так. Во-первых, C# делает позволяет создавать новые типы во время выполнения. Вы просто не можете на них ничего называть статически безопасным способом. Так что да, вы наполовину правы. Но более реалистично вам это нужно, когда вы загружаете сборки во время выполнения, что означает, что тип неизвестен во время компиляции. Если бы вы не могли этого сделать, C# был бы сильно ограничен. Я имею в виду, что вы только что доказали это сами: как еще работает метод Activator, который принимает экземпляр типа? Когда MS писала класс Activator, они не знали во время компиляции о каких-либо будущих типах, которые будут писать пользователи.
@AnorZaken В моем комментарии ничего не говорится о создании типов во время выполнения. Конечно, вы можете это сделать, но вы не можете использовать их статически в одном контексте (конечно, вы можете разместить полную статически скомпилированную программу). Это все, о чем говорится в моем комментарии.
@KonradRudolph Эх, извините, неверно истолковано «такая операция» как означающая создание экземпляра типа, который известен только во время выполнения; вместо использования типа среды выполнения в качестве параметра универсального типа.
@AnorZaken - технически вы можете как создавать новые типы во время выполнения, так и вызывать для них методы статически безопасным способом. если: ваш новый тип реализует известный интерфейс или наследует известный базовый класс. - Любой из этих подходов даст вам статический контракт для вашего объекта, созданного во время выполнения.
Разве обычный T t = new T(); не подойдет?
Фактически, это было бы в общем классе / методе, но не для данного «Типа».
Предполагает, что тип T имеет ограничение new ().
public AbstractType New
{
get
{
return (AbstractType) Activator.CreateInstance(GetType());
}
}
Если вы хотите использовать конструктор по умолчанию, то решение с использованием 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[] был именно тем, что я хотел найти, чтобы узнать, спасибо!
Это довольно просто. Предположим, что ваше имя класса - 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 Это требует жесткого кодирования имени сборки. Для обеспечения гибкости я проверяю null, и код работает динамически :)
Я могу ответить на этот вопрос, потому что я хотел реализовать простой метод 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
Скомпилированное выражение - лучший способ! (для выполнения многократно создавать экземпляр во время выполнения).
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) {}
}
+1 за всю статистику! На данный момент мне не особо нужен такой спектакль, но он все равно очень интересен. :)
Также есть TypeDescriptor.CreateInstance (см. stackoverflow.com/a/17797389/1242), который может быть быстрее при использовании с TypeDescriptor.AddProvider
Это все еще полезно, если вы не знаете, какой тип X находится во время выполнения?
@ajeh Да. Измените typeof (T) на Type.GetType (..).
@ Serj-Tm Нет, это не сработает, если тип X - это среда выполнения Type.
Без использования Reflection:
private T Create<T>() where T : class, new()
{
return new T();
}
Чем это полезно? Вы должны знать тип, чтобы вызвать этот метод, и если вы знаете тип, вы можете создать его без специального метода.
Таким образом, T может меняться во время выполнения. Полезно, если вы работаете с производными типами.
новый T (); завершится ошибкой, если T не является ссылочным типом с конструктором без параметров. Эти методы используют ограничения, чтобы гарантировать, что T является ссылочным типом и имеет конструктор.
Как T может меняться во время выполнения? Разве вам не нужно знать T во время разработки, чтобы вызвать Create?
Если вы работаете с универсальными классами и интерфейсами на фабриках, типы, реализующие интерфейс, которые должны быть созданы, могут различаться.
Вы знаете все возможные типы во время компиляции, но не знаете, какая реализация будет использоваться во время выполнения.
@RobertP. Вы можете создавать новые типы во время выполнения. Нет правила, согласно которому вы знаете все типы во время компиляции. И нет, я не имею в виду дженерики. Вы можете создавать совершенно новые типы и добавлять все поля, свойства, методы и т. д. Во время выполнения. Существует также простой случай, когда вы хотите создать экземпляр типа, который находится в сборке, который неизвестен во время компиляции. Это довольно часто.
Учитывая эту проблему, Activator будет работать, когда есть ctor без параметров. Если это ограничение, рассмотрите возможность использования
System.Runtime.Serialization.FormatterServices.GetSafeUninitializedObject()
Так же, как дополнительный для всех, кто использует приведенные выше ответы, которые реализуют:
ObjectType instance = (ObjectType)Activator.CreateInstance(objectType);
Будьте осторожны - если ваш конструктор не является общедоступным, вы получите следующую ошибку:
"System.MissingMethodException: 'No parameterless constructor defined for this object."
Ваш класс может быть Internal / Friend или любым другим, но конструктор должен быть общедоступным.
Рад, что наконец нашел это, но второй вызов не совсем правильный, отсутствует цитата, а пармы поменяны местами, должно быть: ObjectType instance = (ObjectType) Activator.CreateInstance ("MyAssembly", "MyNamespa ce.ObjectType");