Чи можу я змінити приватне поле для читання лише в C # за допомогою відображення?


115

Мені цікаво, оскільки багато речей можна зробити за допомогою рефлексії, чи можу я змінити приватне поле для читання лише після того, як конструктор закінчив його виконання?
(зверніть увагу: просто цікавість)

public class Foo
{
 private readonly int bar;

 public Foo(int num)
 {
  bar = num;
 }

 public int GetBar()
 {
  return bar;
 }
}

Foo foo = new Foo(123);
Console.WriteLine(foo.GetBar()); // display 123
// reflection code here...
Console.WriteLine(foo.GetBar()); // display 456

Відповіді:


151

Ти можеш:

typeof(Foo)
   .GetField("bar",BindingFlags.Instance|BindingFlags.NonPublic)
   .SetValue(foo,567);

2
Ви абсолютно праві, звичайно. Мої вибачення. І так, я намагався, але я намагався встановити властивість readonly безпосередньо, не використовуючи резервне поле. Те, що я намагався, не мало сенсу. Ваше рішення працює чудово (перевірено ще раз, правильно цього разу)
Sage Pourpre

як ми можемо це зробити з moq?
l - '' '''--------- '' '' '' '' '' '

У ядрі dotnet 3.0 це вже неможливо. System.FieldAccessException кидається: "Неможливо встановити ініціально статичне поле" bar "після того, як тип" Foo "ініціалізується."
Девід Перфорс

54

Очевидна річ - спробувати це:

using System;
using System.Reflection;

public class Test
{
    private readonly string foo = "Foo";

    public static void Main()
    {
        Test test = new Test();
        FieldInfo field = typeof(Test).GetField
            ("foo", BindingFlags.Instance | BindingFlags.NonPublic);
        field.SetValue(test, "Hello");
        Console.WriteLine(test.foo);
    }        
}

Це чудово працює. (У Java є різні правила, що цікаво - ви маєте чітко встановити Fieldдоступність, і вона все одно працюватиме лише для полів, наприклад.)


4
Ахмед - але ми не використовуємо мову для цього, тому мовна специфіка не отримує голосу ...
Марк Гравелл

4
Так - багато що можна зробити, що "ламає" те, що хоче мова. Наприклад, можна запустити ініціалізатори декількох разів, наприклад.
Джон Скіт

28
Зауважу також, що те, що ти можеш сьогодні в якійсь реалізації, не означає, що ти можеш на кожну реалізацію за весь час. Мені невідомо жодне місце, де ми документуємо, що поля, що читаються тільки, повинні бути змінені за допомогою відображення. Наскільки я знаю, відповідна реалізація CLI є абсолютно вільною для впровадження полів для читання лише таким чином, що вони викидають винятки, коли вони мутуються через відображення після того, як конструктор виконаний.
Ерік Ліпперт

3
Це не добре, тому що є випадки, коли мені потрібно розширити клас більше, ніж це було спочатку розроблено. Завжди повинен бути спосіб перекрити інкапсуляцію, коли це добре сплановано. Єдині альтернативи - горі, а іноді неможливі без переписання частини фреймворку.
дрифтер

5
@drifter: У цей момент ти відкриваєш себе перед світом болю. Ви покладаєтесь на поточні деталі впровадження, які можна легко змінити в майбутній версії.
Джон Скіт

11

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

Однак ми також помітили ще одне питання. Якщо ви використовуєте код у середовищі з обмеженими дозволами, ви можете отримати виняток.

У нас щойно був випадок, коли наш код добре працював на наших машинах, але ми отримали, VerificationExceptionколи код працював у обмеженому середовищі. Винуватцем став роздум про дзвінок сетеру в поле для читання. Це спрацювало, коли ми зняли лише обмеження для цього поля.


2
Було б цікаво дізнатись, в які середовища кидають VerificationException
Сергій Жуков

4

Ви запитали, чому ви хочете розірвати інкапсуляцію так.

Я використовую клас помічників сутності для гідратації об'єктів. Це використовує відображення для отримання всіх властивостей нового порожнього об'єкта, а також відповідає властивості / імені поля стовпцю в наборі результатів і встановлює його, використовуючи propertyinfo.setvalue ().

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

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


1
Я також використовував його, щоб подолати деякі обмеження api, де значення було жорстко закодовано, або потрібен конфігураційний файл, який я не можу надати. (Розмір файлу WSE 2.0 для вкладень DIME, коли збірки завантажувались, наприклад, через відображення)
StingyJack

3

Ще один простий спосіб зробити це за допомогою небезпечного (або ви можете передати поле методу C через DLLImport і встановити його туди).

using System;

namespace TestReadOnly
{
    class Program
    {
        private readonly int i;

        public Program()
        {
            i = 66;
        }

        private unsafe void ForceSet()
        {
            fixed (int* ptr = &i) *ptr = 123;
        }

        static void Main(string[] args)
        {
            var program = new Program();
            Console.WriteLine("Contructed Value: " + program.i);
            program.ForceSet();
            Console.WriteLine("Forced Value: " + program.i);
        }
    }
}

2

Відповідь - так, але важливіше:

Чому б ви хотіли? Навмисне порушення інкапсуляції здається мені жахливо поганою ідеєю.

Використовувати рефлексію для зміни рівня читання або постійного поля - це як поєднання Закону про ненавмисні наслідки із Законом Мерфі .


1
відповідь - "просто цікавість", як згадувалося вище.
Рон Кляйн

Бувають випадки, коли мені доводиться робити саме цю хитрість, щоб написати найкращий код, який я можу. Справа в точці - elegantcode.com/2008/04/17/testing-a-membership-provider
sparker

3
Я також використовую цей трюк в тестових проектах, щоб змінити значення за замовчуванням, які не слід змінювати в жодному бізнес-коді ...
Коен

І я намагався встановити приватні внутрішні властивості в бібліотеках базового класу для тестових цілей, зокрема API членства, де MS позначала все приватне, внутрішнє та без встановлення властивостей. Для цього є випадки, але ви правильні, якщо питання стосується API, який знаходиться під вашим контролем
Чад Грант,

3
Бувають ситуації, коли це має сенс. O / R-картографи, такі як NHibernate, роблять це постійно для гідратації, оскільки це єдиний спосіб реалізувати в першу чергу інкапсуляцію даних для стійких осіб.
chris

2

Не робіть цього.

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

Модифікація поля, що читається тільки працювало один раз. Але якщо ви спробуєте її знову змінити, у вас вийдуть такі ситуації:

SoundDef mySound = Reflection_Modified_Readonly_SoundDef_Field;
if( !(mySound is SoundDef) )
    Log("Welcome to impossible-land!"); //This would run

Тож не робіть цього.

Це було в режимі виконання Mono (Unity game engine).


2
FYI - Двигун Unity не може бути використаний для ефективної відповіді на глибокі питання, пов'язані з мовою C #, на кшталт цього, оскільки Unity виконує власну компіляцію C #, як ніби .cs у певному сенсі є сценаріями. Я не кажу, що ваша думка не вірна, але це, безумовно, специфічно для Unity Engine & C # разом.
Дейв Джеллісон

0

Я просто хочу додати, що якщо вам потрібно зробити цей матеріал для тестування одиниць, ви можете використовувати:

A) PrivateObject клас

В) Вам все одно знадобиться екземпляр PrivateObject, але ви можете генерувати об’єкти "Accessor" за допомогою Visual Studio. Як: відновлювати приватні підключення

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

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