Как использовать отражение для вызова частного метода?
В моем классе существует группа частных методов, и мне нужно динамически называть ее на основе входного значения. И вызывающий код, и целевые методы находятся в одном экземпляре. Код выглядит следующим образом:
MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType);
dynMethod.Invoke(this, new object[] { methodParams });
В этом случае GetMethod()
не вернет частные методы. Что мне нужно BindingFlags
для поставки GetMethod()
, чтобы он мог найти частные методы?
Ответы
Ответ 1
Просто измените свой код, чтобы использовать перегруженную версию GetMethod
, которая принимает BindingFlags:
MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType,
BindingFlags.NonPublic | BindingFlags.Instance);
dynMethod.Invoke(this, new object[] { methodParams });
Здесь Документация перечисления BindingFlags.
Ответ 2
BindingFlags.NonPublic
не вернет никаких результатов сам по себе. Как оказалось, объединение его с BindingFlags.Instance
делает трюк.
MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType,
BindingFlags.NonPublic | BindingFlags.Instance);
Ответ 3
И если вы действительно хотите попасть в проблему, упростите ее выполнение, написав метод расширения:
static class AccessExtensions
{
public static object call(this object o, string methodName, params object[] args)
{
var mi = o.GetType ().GetMethod (methodName, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance );
if (mi != null) {
return mi.Invoke (o, args);
}
return null;
}
}
И использование:
class Counter
{
public int count { get; private set; }
void incr(int value) { count += value; }
}
[Test]
public void making_questionable_life_choices()
{
Counter c = new Counter ();
c.call ("incr", 2); // "incr" is private !
c.call ("incr", 3);
Assert.AreEqual (5, c.count);
}
Ответ 4
Microsoft недавно изменила API отражения, сделав большинство этих ответов устаревшими. Следующее должно работать на современных платформах (включая Xamarin.Forms и UWP):
obj.GetType().GetTypeInfo().GetDeclaredMethod("MethodName").Invoke(obj, yourArgsHere);
Или как метод расширения:
public static object InvokeMethod<T>(this T obj, string methodName, params object[] args)
{
var type = typeof(T);
var method = type.GetTypeInfo().GetDeclaredMethod(methodName);
return method.Invoke(obj, args);
}
Замечания:
-
Если требуемый метод находится в суперклассе obj
универсальный T
должен быть явно установлен в тип суперкласса.
-
Если метод асинхронный, вы можете использовать await (Task) obj.InvokeMethod(…)
.
Ответ 5
Вы абсолютно уверены, что это невозможно сделать через наследование? Отражение - это самое последнее, на что вы должны обратить внимание при решении проблемы, это затрудняет рефакторинг, понимание вашего кода и любой автоматизированный анализ.
Похоже, у вас должен быть класс DrawItem1, DrawItem2 и т.д., которые переопределяют ваш dynMethod.
Ответ 6
Отражение, особенно в отношении частных лиц, неверно
- Отражение нарушает тип безопасности. Вы можете попытаться вызвать метод, который не существует (больше), или с неправильными параметрами, или с слишком большим количеством параметров, или недостаточно... или даже в неправильном порядке (этот мой любимый :)). Кстати, тип возвращаемого значения также может измениться.
- Отражение медленное.
Отражение закрытых членов нарушает принцип инкапсуляции и тем самым подвергает ваш код следующему:
- Увеличьте сложность вашего кода, потому что он должен обрабатывать внутреннее поведение классов. То, что скрыто, должно оставаться скрытым.
- Облегчает взлом вашего кода, так как он будет компилироваться, но не будет работать, если метод изменил свое имя.
- Позволяет легко взломать приватный код, потому что если он приватный, его не нужно называть таким образом. Возможно, закрытый метод ожидает некоторое внутреннее состояние перед вызовом.
Что если я все равно должен это сделать?
Бывают случаи, когда вы зависите от третьей стороны или вам нужно, чтобы какой-то API-интерфейс не был раскрыт, вам нужно подумать. Некоторые также используют его для тестирования некоторых классов, которыми они владеют, но они не хотят менять интерфейс, чтобы предоставить доступ к внутренним элементам только для тестов.
Если вы делаете это, делайте это правильно
Чтобы смягчить проблему, которую легко сломать, лучше всего обнаружить любой потенциальный сбой путем тестирования в модульных тестах, которые будут выполняться в сборке с непрерывной интеграцией или тому подобном. Конечно, это означает, что вы всегда используете одну и ту же сборку (которая содержит закрытые элементы). Если вы используете динамическую нагрузку и отражение, вам нравится играть с огнем, но вы всегда можете поймать исключение, которое может вызвать вызов.
- Смягчить медлительность отражения:
В последних версиях.Net Framework CreateDelegate в 50 раз превосходил вызов MethodInfo:
// The following should be done once since this does some reflection
var method = this.GetType().GetMethod("Draw_" + itemType,
BindingFlags.NonPublic | BindingFlags.Instance);
// Here we create a Func that targets the instance of type which has the
// Draw_ItemType method
var draw = (Func<TInput, Output[]>)_method.CreateDelegate(
typeof(Func<TInput, TOutput[]>), this);
вызовы draw
будут примерно в 50 раз быстрее, чем MethodInfo.Invoke
использует draw
в качестве стандартного Func
:
var res = draw(methodParams);
Проверьте этот пост, чтобы увидеть эталонный тест по различным вызовам методов.
Ответ 7
Не могли бы вы просто использовать другой метод Draw для каждого типа, который вы хотите рисовать? Затем вызовите перегруженный метод Draw, проходящий в объекте типа itemType для рисования.
В вашем вопросе не указывается, действительно ли itemType относится к объектам разных типов.
Ответ 8
Я думаю, вы можете передать его BindingFlags.NonPublic
, где это метод GetMethod
.
Ответ 9
Вызывает любой метод, несмотря на его уровень защиты на экземпляре объекта. Наслаждайтесь!
public static object InvokeMethod(object obj, string methodName, params object[] methodParams)
{
var methodParamTypes = methodParams?.Select(p => p.GetType()).ToArray() ?? new Type[] { };
var bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static;
MethodInfo method = null;
var type = obj.GetType();
while (method == null && type != null)
{
method = type.GetMethod(methodName, bindingFlags, Type.DefaultBinder, methodParamTypes, null);
type = type.BaseType;
}
return method?.Invoke(obj, methodParams);
}
Ответ 10
BindingFlags.NonPublic
Ответ 11
Прочтите этот (дополнительный) ответ (иногда это ответ), чтобы понять, к чему это идет и почему некоторые люди в этой теме жалуются, что "это все еще не работает"
Я написал точно такой же код, как один из ответов здесь. Но у меня все еще была проблема. Я поставил точку останова на
var mi = o.GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance );
Выполнено, но mi == null
И так продолжалось до тех пор, пока я не "перестроил" все вовлеченные проекты. Я тестировал одну сборку, пока метод отражения находился в третьей сборке. Это было довольно странно, но я использовал Immediate Window для обнаружения методов и обнаружил, что закрытый метод, который я пытался выполнить модульным тестом, имел старое имя (я переименовал его). Это говорит мне о том, что старая сборка или PDB все еще существует, даже если собирается unit тестовый проект - по какой-то причине проект, который он тестировал, не был построен. "перестроить" сработало