Як використовувати відображення для виклику приватного методу?


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 .


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дозволить вам отримати методи з батьківських класів до вашого примірника.
Dragonthoughts

67

BindingFlags.NonPublicне поверне жодних результатів сам по собі. Як виявилося, поєднання його з цим BindingFlags.Instanceробить трюк.

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

internal
Ця

У мене схожа проблема. Що робити, якщо "це" - це дочірній клас, і ви спробуєте викликати приватний метод батьків?
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);
    }

14
Небезпечні? Так. Але прекрасне допоміжне розширення, коли я перебуваю в моєму просторі тестування простору імен. Дякую за це
Роберт Валер

5
Якщо ви переймаєтесь справжніми винятками, викинутими з методу, який називається, це гарна ідея, щоб перетворити його на блок спробу вилову, а замість цього знову перекинути внутрішній виняток, коли TargetInvokationException застала. Я роблю це у своєму розширенні для помічника тесту для підрозділу.
Слободан Савкович

2
Відбиття небезпечно? Хммм ... C #, Java, Python ... насправді все небезпечно, навіть світ: D Ви просто повинні подбати про те, як зробити це безпечно ...
Легенди

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
@Bill K: Враховуючи інші обставини, ми вирішили не використовувати спадщину для цього, отже, і використання роздумів. У більшості випадків ми зробили б це так.
Джеромі Ірвін

8

Рефлексія, особливо про приватних членів, неправильна

  • Відбиття порушує безпеку типу. Ви можете спробувати викликати метод, який не існує (більше), або з неправильними параметрами, або з занадто великими параметрами, або недостатньо ... або навіть у неправильному порядку (цей мій улюблений :)). До речі, тип повернення також може змінитися.
  • Рефлексія повільна.

Відображення приватних членів порушує принцип інкапсуляції і, таким чином, виставляє ваш код наступному:

  • Збільште складність свого коду, оскільки він повинен керувати внутрішньою поведінкою класів. Те, що приховано, має залишатися прихованим.
  • Робить код зручним, оскільки він буде компілюватися, але не запуститься, якщо метод змінив ім'я.
  • Робить приватний код зручним, тому що якщо він приватний, його не потрібно називати таким чином. Можливо, приватний метод очікує деякого внутрішнього стану, перш ніж викликати.

Що робити, якщо я все одно повинен це зробити?

Бувають такі випадки, коли ти залежиш від третьої сторони або тобі потрібні певні апі, які не піддаються впливу, то ти повинен зробити певне роздуми. Деякі також використовують його для тестування деяких класів, якими вони володіють, але вони не хочуть змінювати інтерфейс, щоб надати доступ до внутрішніх членів лише для тестів.

Якщо ви це зробите, зробіть це правильно

  • Пом’якшіть просту ламу:

Щоб пом'якшити проблему, яку можна легко зламати, найкраще - виявити будь-який потенційний зрив, випробувавши в одиничних тестах, які працюватимуть у безперервній збірці інтеграції тощо. Звичайно, це означає, що ви завжди використовуєте ту саму збірку (яка містить приватних членів). Якщо ви використовуєте динамічне навантаження та роздуми, вам подобається грати з вогнем, але ви завжди можете зловити виняток, який може викликати виклик.

  • Пом'якшити повільність відображення:

В останніх версіях .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
Хоча я вважаю, що введення залежності повинно бути кращим способом тестування одиниць, я вважаю, що обережно використовувати не зовсім зло для використання рефлексії для одиниць доступу, які в іншому випадку були б недоступні для тестування. Особисто я думаю, як і звичайні [публічні] [захищені] [приватні] модифікатори, у нас також повинні бути модифікатори [Тест] [Склад], тому на цих етапах можна побачити певні речі, не змушуючи їх робити все публічним ( і тому доведеться повністю документувати ці методи)
andrew pate

1
Дякую Fab за перерахування проблем роздумів про приватних членів, це змусило мене переглянути, як я ставлюсь до цього, і я дійшов висновку ... Використовувати роздуми для тестування одиниці ваших приватних членів неправильно, однак залишати кодові шляхи неперевіреними дійсно дійсно не так.
Андре паште

2
Але коли мова йде про тестування одиниць, як правило, застарілого коду, який не перевіряється, - це геніальний спосіб зробити це
TS

2

Не могли б ви просто встановити інший метод малювання для кожного типу, який потрібно намалювати? Потім зателефонуйте перевантаженому методу Draw, що передає об'єкт типу itemType, який потрібно намалювати.

У вашому запитанні не з’ясовується, чи справді itemType відноситься до об'єктів різного типу.



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

І така поведінка продовжувалась так, доки я не "перебудовувався" на всі залучені проекти. Я тестував одну збірку, тоді як метод відбиття сидів у третій збірці. Це було зовсім заплутано, але я використовував негайне вікно для виявлення методів, і я виявив, що приватний метод, який я намагався провести тест, мав стару назву (я перейменував його). Це сказало мені, що стара збірка або PDB все ще є, навіть якщо проект тестового проекту побудований - чомусь проект, який його тести не будували. "перебудувати" спрацювало


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.