Создание экземпляра класса с внутренним конструктором
Автор вопроса: Michael StumОтветов: 9
У меня есть класс, конструктор которого определен как внутренний, а это значит, что я не могу его создать. Хотя это может иметь смысл, я все же хотел бы сделать это один раз для отладки и исследовательских целей.
Можно ли сделать это с помощью Reflection? Я знаю, что могу получить доступ к частным / внутренним членам, но могу ли я вызвать внутренний конструктор?
Или, поскольку конструктор не делает ничего важного, могу ли я использовать отражение, чтобы сказать: "Послушайте, просто дайте мне экземпляр класса, не вызывая конструктор, я сделаю это вручную"?
Производительность и "стабильность" здесь не проблема, так как это не производственный код.
Изменить: в качестве пояснения: к сожалению, я не контролирую другую сборку и не имею ее исходного кода, я просто пытаюсь понять, как она работает, поскольку ее документации почти не существует, но я должен взаимодействовать с этим.
Существует метод FormatterServices.GetUninitializedObject ( Namespace: System.Runtime.Serialization), он якобы не вызывает конструкторов, если вы действительно хотите опробовать этот подход.
Ответ 2 · рейтинг 93 · Philippe Leybaert
В качестве альтернативы можно было бы назначить вызывающую сборку как "дружественную" сборку.
Просто добавьте это в файл AssemblyInfo.cs сборки, содержащей внутренний конструктор:
public static T CreateInstance<T>(params object[] args)
{
var type = typeof (T);
var instance = type.Assembly.CreateInstance(
type.FullName, false,
BindingFlags.Instance | BindingFlags.NonPublic,
null, args, null, null);
return (T) instance;
}
Пример использования (это тип SDK Kinect, который мне нужно было создать для модульных тестов):
DiscreteGestureResult a = CreateInstance<DiscreteGestureResult>(false, false, 0.5f);
Ответ 4 · рейтинг 3 · Michael A. McCloskey
Некоторое время назад я столкнулся с такой же ситуацией и создал небольшую утилиту, которую назвал "InternalsVisibleToInjector". Он использует ILDASM и ILASM для дизассемблирования, изменения, повторной сборки и сборки с добавлением имени сборки по моему выбору в список InternalsVisibleTo для целевой сборки. В моей ситуации это сработало.
Я разместил исходный код (VS 2008 C # WinForm) для утилиты здесь:
Это может не сработать, если сборка подписана. Пожалуйста, примите все необходимые меры предосторожности (т.е. сделайте резервную копию сборки перед ее использованием и убедитесь, что у вас есть твердые юридические основания и т. Д.)
Ответ 5 · рейтинг 3 · Xavier John
Если вы хотите избежать отражения, вы можете использовать выражения. Вот пример вызова частного конструктора со строковым значением.
private static Func<string, T> CreateInstanceFunc()
{
var flags = BindingFlags.NonPublic | BindingFlags.Instance;
var ctor = typeof(T).GetConstructors(flags).Single(
ctors =>
{
var parameters = ctors.GetParameters();
return parameters.Length == 1 && parameters[0].ParameterType == typeof(string);
});
var value = Expression.Parameter(typeof(string), "value");
var body = Expression.New(ctor, value);
var lambda = Expression.Lambda<Func<string, T>>(body, value);
return lambda.Compile();
}
Ответ 6 · рейтинг 3 · Tore Aurstad
Вот более практический пример. Я хочу создать экземпляр ObjectMaterializedEventArg из Entity Framework. Выглядит это так:
namespace System.Data.Entity.Core.Objects
{
/// <summary>EventArgs for the ObjectMaterialized event.</summary>
public class ObjectMaterializedEventArgs : EventArgs
{
private readonly object _entity;
internal ObjectMaterializedEventArgs(object entity)
{
this._entity = entity;
}
/// <summary>Gets the entity object that was created.</summary>
/// <returns>The entity object that was created.</returns>
public object Entity
{
get { return this._entity; }
}
}
}
Как мы видим, это событие arg использует только внутренний конструктор.
Чтобы выполнить модульное тестирование правила расшифровки пациента в программной системе, с которой я работаю, нам нужно создать экземпляр такого объекта, поэтому в итоге я использовал вместо этого метод GetConstructors.
[Test]
public void EmptyNameAndOfficialIdDoesNotThrow()
{
var patientDecryptingRule = new PatientDecryptingRule();
object[] reservation = new object[]
{
new Operation
{
Status = (int) OperationStatusDataContract.Reservation,
Patient = new Patient
{
Name = null,
OfficialId = null,
IsPatientEncrypted = true
}
}
};
var relevantConstructor = typeof(ObjectMaterializedEventArgs).GetConstructors(
BindingFlags.NonPublic | BindingFlags.Instance).FirstOrDefault();
ObjectMaterializedEventArgs objectMaterializedEventArgs =
(ObjectMaterializedEventArgs) relevantConstructor?.Invoke(reservation);
patientDecryptingRule.ModifyObjectMaterialized(objectMaterializedEventArgs);
}
Я использую здесь GetConstructors, указывая, что конструктор, который нужно искать, не является общедоступным (например, внутренний) и экземпляр как bindingflags, затем используйте FirsOrDefault.
Надеюсь, этот сценарий будет полезен тем, у кого есть проблемы с GetConstructor правильным ответом. Вместо этого вы можете использовать GetConstructors и при необходимости отфильтровать еще несколько. Затем в ?.Invoke() передайте массив объектов ваших параметров.
Ответ 7 · рейтинг 1 · Peter Lillevold
Вы можете использовать Reflector, чтобы проанализировать его исходный код и, исходя из этого, выяснить внутреннюю работу. .
Ответ 8 · рейтинг 1 · outbred
Если кто-то снова наткнется на это, вот пример того, как поднять его с помощью отражения:
var args = FormatterServices.GetUninitializedObject(typeof(SizeChangedEventArgs)) as SizeChangedEventArgs;
Debug.Assert(args != null);
var field = typeof(SizeChangedEventArgs).GetField("_previousSize", BindingFlags.Instance | BindingFlags.NonPublic);
Debug.Assert(field != null);
field.SetValue(args, new Size(0,0));
field = typeof(SizeChangedEventArgs).GetField("_element", BindingFlags.Instance | BindingFlags.NonPublic);
Debug.Assert(field != null);
field.SetValue(args, GraphicsWrapper);
field = typeof(RoutedEventArgs).GetField("_source", BindingFlags.Instance | BindingFlags.NonPublic);
Debug.Assert(field != null);
field.SetValue(args, GraphicsWrapper);
field = typeof(RoutedEventArgs).GetField("_routedEvent", BindingFlags.Instance | BindingFlags.NonPublic);
Debug.Assert(field != null);
field.SetValue(args, SizeChangedEvent);
GraphicsWrapper.RaiseEvent(args);
... где GraphicsWrapper - это элемент управления WPF, из которого вы хотите его поднять.
Ответ 9 · Brian Rasmussen
internal не означает, что вы не можете создать его экземпляр. Это просто означает, что его могут вызывать только члены одной сборки.