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

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

MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType);
dynMethod.Invoke(this, new object[] { methodParams });

В этом случае, GetMethod() не будет возвращать частные методы. Какие BindingFlags мне нужно поставить GetMethod() чтобы он мог найти частные методы?

13 ответов

Решение

Просто измените свой код, чтобы использовать перегруженную версиюGetMethod который принимает BindingFlags:

MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType, 
    BindingFlags.NonPublic | BindingFlags.Instance);
dynMethod.Invoke(this, new object[] { methodParams });

Вот документация перечисления BindingFlags.

BindingFlags.NonPublic не вернет никаких результатов сам по себе. Как оказалось, совмещая это с BindingFlags.Instance делает трюк.

MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType, 
    BindingFlags.NonPublic | BindingFlags.Instance);

И если вы действительно хотите попасть в неприятности, сделайте это проще, написав метод расширения:

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);
    }

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 generic должен быть явно установлен на тип суперкласса.

  • Если метод асинхронный, вы можете использовать await (Task) obj.InvokeMethod(…),

Отражение, особенно в отношении частных лиц, неверно

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

Отражение закрытых членов нарушает принцип инкапсуляции и тем самым подвергает ваш код следующему:

  • Увеличьте сложность вашего кода, потому что он должен обрабатывать внутреннее поведение классов. То, что скрыто, должно оставаться скрытым.
  • Облегчает взлом вашего кода, так как он будет компилироваться, но не будет работать, если метод изменил свое имя.
  • Позволяет легко взломать приватный код, потому что если он приватный, его не нужно называть таким образом. Возможно, закрытый метод ожидает некоторое внутреннее состояние перед вызовом.

Что если я все равно должен это сделать?

Бывают случаи, когда вы зависите от третьей стороны или вам нужно, чтобы какой-то 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);

Проверьте этот пост, чтобы увидеть эталонный тест по различным вызовам методов.

Вы абсолютно уверены, что это не может быть сделано с помощью наследования? Отражение - это самое последнее, на что вы должны смотреть при решении проблемы, оно усложняет рефакторинг, понимание вашего кода и любой автоматический анализ.

Похоже, у вас должен быть класс DrawItem1, DrawItem2 и т. Д., Который переопределяет ваш dynMethod.

Следует отметить, что вызов из производного класса может быть проблематичным.

Возможны ошибки:

      this.GetType().GetMethod("PrivateTestMethod", BindingFlags.Instance | BindingFlags.NonPublic)

Верный:

      typeof(CurrentClass).GetMethod("PrivateTestMethod", BindingFlags.Instance | BindingFlags.NonPublic)

Не могли бы вы просто иметь разные методы Draw для каждого типа, который вы хотите рисовать? Затем вызовите перегруженный метод Draw, передавая объект типа itemType для рисования.

Ваш вопрос не дает понять, действительно ли itemType относится к объектам разных типов.

Я думаю, что вы можете пройти BindingFlags.NonPublic где это GetMethod метод.

Вызывает любой метод, несмотря на его уровень защиты на экземпляре объекта. Наслаждайтесь!

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);
}

Прочтите этот (дополнительный) ответ (иногда это ответ), чтобы понять, к чему это идет и почему некоторые люди в этой теме жалуются, что "это все еще не работает"

Я написал точно такой же код, как один из ответов здесь. Но у меня все еще была проблема. Я поставил точку останова на

var mi = o.GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance );

Выполнено но mi == null

И так продолжалось до тех пор, пока я не "перестроил" все вовлеченные проекты. Я тестировал одну сборку, пока метод отражения находился в третьей сборке. Это было довольно странно, но я использовал Immediate Window для обнаружения методов и обнаружил, что закрытый метод, который я пытался выполнить модульным тестом, имел старое имя (я переименовал его). Это говорит мне о том, что старая сборка или PDB все еще существует, даже если собирается модульный тестовый проект - по какой-то причине проект, который он тестировал, не был построен. "перестроить" сработало

BindingFlags.NonPublic

В предварительной версии .NET 8 компания Microsoft представила альтернативный подход к доступу к частным методам, который обеспечивает более высокую производительность по сравнению с использованием отражения.

Ниже приведена краткая демонстрация этой новой функции:

      public class Caller
{
    [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "MethodName"]
    public static extern string GetMethod(MyClassName obj);
}

var method = Caller.GetMethod(this);

Для более четкого понимания преимуществ производительности приведем сравнение нового «небезопасного» подхода с традиционным методом отражения и обычным вызовом метода.

Вот сравнительная визуализация результатов:

Из графика видно, что небезопасный метод работает значительно быстрее, чем его аналог с отражением.

Примечание. Хотя этот метод быстрее, всегда убедитесь, что вы понимаете последствия использования «небезопасных» операций в вашем коде. Не зря его называют «небезопасным». Например, этот способ позволяет вам установить значения длясвойства, которые не должны быть возможны.

Другие вопросы по тегам