Ответ 1
A FormatterServices.GetUninitializedObject существует метод (пространство имен: System.Runtime.Serialization), он якобы не вызывает конструкторов, если вы действительно хотите попробовать этот подход.
У меня есть класс, конструктор которого определяется как внутренний, что означает, что я не могу его создать. Хотя это может иметь смысл, я все равно хотел бы сделать это один раз для отладки и исследований.
Можно ли сделать это с помощью Reflection? Я знаю, что могу получить доступ к частным/внутренним членам, но могу ли я вызвать внутренний конструктор?
Или, поскольку конструктор не делает ничего важного, могу ли я использовать отражение, чтобы сказать "Посмотрите, просто дайте мне экземпляр класса без вызова конструктора, я сделаю это вручную"?
Производительность и "стабильность" здесь не проблема, так как это не производственный код.
Изменить: Точно так же, как уточнение: К сожалению, я не контролирую другую сборку и не имею ее исходного кода, я просто пытаюсь понять, как она работает, поскольку она находится рядом с не- -existent, но я должен взаимодействовать с ним.
A FormatterServices.GetUninitializedObject существует метод (пространство имен: System.Runtime.Serialization), он якобы не вызывает конструкторов, если вы действительно хотите попробовать этот подход.
Альтернативой было бы назначить вызывающую сборку как сборку "друг".
Просто добавьте это в файл AssemblyInfo.cs сборки, содержащей внутренний конструктор:
[assembly: InternalsVisibleTo("Calling.Assembly")]
Если у вас нет доступа к сборке, вы также можете вызвать конструктор напрямую (используя Reflection):
MyClass obj = (MyClass) typeof(MyClass).GetConstructor(
BindingFlags.NonPublic | BindingFlags.Instance,
null, Type.EmptyTypes, null).Invoke(null);
Это метод, полученный из этого ответа:
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;
}
Пример использования (это тип Kinect SDK, который мне нужно создать для модульных тестов):
DiscreteGestureResult a = CreateInstance<DiscreteGestureResult>(false, false, 0.5f);
Я пережил эту же ситуацию некоторое время назад и создал небольшую утилиту, которую я назвал "InternalsVisibleToInjector". Он использует ILDASM и ILASM для демонтажа, модификации и сборки и сборки с именем сборки моего выбора, добавленным в список InternalsVisibleTo для целевой сборки. В моей ситуации он работал очень хорошо.
Я разместил исходный код (VS 2008 С# WinForm) для утилиты здесь:
http://www.schematrix.com/downloads/InternalsVisibleToInjector.zip
Это может не работать, если сборка подписана. Примите все необходимые меры предосторожности (то есть сделайте резервную копию сборки перед ее использованием и убедитесь, что вы находитесь на твердой юридической основе и т.д.)
Если вы хотите избежать отражения, вы можете использовать выражения. Ниже приведен пример вызова частного конструктора со строковым значением.
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();
}
В случае, если кто-то снова наткнется на это, вот пример того, как поднять его через отражение:
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, с которым вы хотите его повысить.
Вот более практичный пример. Я хочу создать экземпляр ObjectMaterializedEventArg из Entity Framework. Это выглядит так:
namespace System.Data.Entity.Core.Objects
{ ///EventArgs для события ObjectMaterialized. открытый класс ObjectMaterializedEventArgs: EventArgs { закрытый объект только для чтения _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;
}
}
} }
Как мы видим, это событие имеет только внутренний конструктор.
Чтобы выполнить модульное тестирование для правила расшифровки пациента в программной системе, с которой я работаю, нам нужно создать экземпляр такого объекта, поэтому я вместо этого использовал метод 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, определяя, что конструкторы, которые нужно искать, не являются публичными (например, внутренними) и экземпляром в качестве флагов привязки, а затем используем FirsOrDefault.
Надеюсь, что этот сценарий полезен для тех, у кого возникают проблемы с корректной версией GetConstructor. Вместо этого вы можете использовать GetConstructors и при необходимости отфильтровать еще. Затем в .Invoke передайте в массив объектов ваши параметры.
internal
не означает, что вы не можете его создать. Это просто означает, что только члены из той же сборки могут это назвать.
В целях тестирования вы можете позволить вашей тестовой сборке получить доступ к внутренним элементам, используя атрибут InternalsVisibleTo. См. http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.internalsvisibletoattribute.aspx
Вы можете использовать Reflector, чтобы проанализировать его исходный код и выяснить, что такое внутренняя работа.