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

326

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

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

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

Джероми Ирвин
источник

Ответы:

498

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

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

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

wprl
источник
248
Я попаду в такие неприятности.
Фрэнк Швитерман
1
BindingFlags.NonPublicне возвращается privateметод .. :(
Moumit
4
@MoumitMondal твои методы статичны? Вы должны указать, BindingFlags.Instanceа также BindingFlags.NonPublicдля нестатических методов.
BrianS
Нет @BrianS .. метод non-staticи privateкласс унаследован от System.Web.UI.Page.. это все равно делает меня дураком .. я не нашел причину .. :(
Moumit
3
Добавление BindingFlags.FlattenHierarchyпозволит вам получить методы из родительских классов к вашему экземпляру.
Драконьи мысли
67

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

MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType, 
    BindingFlags.NonPublic | BindingFlags.Instance);
Джероми Ирвин
источник
Та же логика применима и к internalфункциям
supertopi
У меня аналогичная проблема. Что если «this» является дочерним классом и вы пытаетесь вызвать приватный метод родителя?
persianLife
Можно ли использовать это для вызова защищенных методов класса base.base?
Шив
51

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

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);
    }
cod3monk3y
источник
14
Опасные? Да. Но отличное вспомогательное расширение, когда оно включено в мое пространство имен модульного тестирования. Спасибо за это.
Роберт Валер
5
Если вы заботитесь о реальных исключениях, вызванных вызванным методом, хорошей идеей будет заключить его в блок try catch и перебросить внутреннее исключение вместо того, чтобы перехватить TargetInvokationException. Я делаю это в своем дополнительном модульном тесте.
Слободан Савкович
2
Отражение опасно? Хммм ... C #, Java, Python ... на самом деле все опасно, даже мир: D Надо просто позаботиться о том, как сделать это безопасно ...
Legends
16

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

Примечание:

  • Если нужный метод в суперкласс родовое должен быть явно установлен на тип суперкласса.objT

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

Оуэн Джеймс
источник
Он не работает для UWP .net версии, по крайней мере, потому что он работает только для открытых методов: « Возвращает коллекцию, которая содержит все открытые методы, объявленные для текущего типа, которые соответствуют указанному имени ».
Дмитрий Бондаренко
1
@DmytroBondarenko Я проверил это по частным методам, и это сработало. Я видел это, однако. Не уверен, почему он ведет себя иначе, чем документация, но, по крайней мере, он работает.
Оуэн Джеймс
Да, я бы не назвал все остальные ответы устаревшими, если в документации сказано, GetDeclareMethod()что они предназначены для извлечения только публичного метода.
Mass Dot Net
10

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

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

Билл К
источник
1
@ Билл К: Учитывая другие обстоятельства, мы решили не использовать наследование для этого, следовательно, использование отражения. В большинстве случаев мы бы так и сделали.
Джером Ирвин
8

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

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

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

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

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

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

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

потрясающий
источник
1
Хотя я понимаю, что внедрение зависимостей должно быть предпочтительным способом юнит-тестирования, я думаю, что использование с осторожностью не является абсолютно злым, если использовать отражение для доступа к блокам, которые в противном случае были бы недоступны для тестирования. Лично я думаю, что наряду с обычными модификаторами [public] [protected] [private] у нас также должны быть модификаторы [Test] [Composition], чтобы на этих этапах можно было видеть некоторые вещи без необходимости делать все полностью публичным ( и , следовательно , должны полностью документировать эти методы)
андрей паштет
1
Спасибо Fab за то, что я перечислил проблемы рефлексии на приватных участниках, это заставило меня проверить, что я чувствую по поводу его использования, и пришел к выводу ... Использовать рефлексию для юнит-тестирования ваших приватных участников неправильно, однако оставлять пути кода непроверенными действительно очень неправильно.
андрей паштет
2
Но когда дело доходит до модульного тестирования унаследованного кода, обычно не тестируемого модулем, это великолепный способ сделать это
TS
2

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

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

Питер Хессион
источник
1

Я думаю , что вы можете передать его , BindingFlags.NonPublicгде он является GetMethodметодом.

Армин Ронахер
источник
1

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

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);
}
Максим Шамихулау
источник
0

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

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

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

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

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

TS
источник