Как использовать отражение для вызова частного метода?

В моем классе существует группа частных методов, и мне нужно динамически называть ее на основе входного значения. И вызывающий код, и целевые методы находятся в одном экземпляре. Код выглядит следующим образом:

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 тестовый проект - по какой-то причине проект, который он тестировал, не был построен. "перестроить" сработало