Могу ли я изменить частное поле только для чтения в 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
Вы, конечно, абсолютно правы. Мои извинения. И да, я пытался, но я попытался установить свойство только для чтения напрямую, не используя поле поддержки. То, что я предпринял, не имело смысла. Ваше решение работает отлично (снова протестировано, на этот раз правильно)
Sage Pourpre
как мы можем сделать это с помощью moq?
l --''''''--------- '' '' '' '' '' ''
В dotnet core 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когда код выполнялся в ограниченной среде. Виной всему был рефлексивный вызов установщика поля только для чтения. Это сработало, когда мы удалили ограничение только для чтения для этого поля.

Andreas
источник
2
Было бы интересно узнать, в каких средах возникает исключение VerificationException
Сергей Жуков
4

Вы спросили, почему вы хотите нарушить такую ​​инкапсуляцию.

Я использую вспомогательный класс сущностей для гидратации сущностей. Это использует отражение, чтобы получить все свойства новой пустой сущности, и сопоставляет имя свойства / поля со столбцом в наборе результатов и устанавливает его с помощью propertyinfo.setvalue ().

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

Многие из моих сохраненных процедур возвращают наборы результатов, которые не соответствуют напрямую таблицам или представлениям, поэтому ORM кодового поколения ничего для меня не делают.

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

Другой простой способ сделать это - использовать unsafe (или вы можете передать поле методу 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);
        }
    }
}
zezba9000
источник
2

Ответ положительный, но что более важно:

Зачем тебе это нужно? Намеренно нарушать инкапсуляцию мне кажется ужасно плохой идеей.

Использование отражения для изменения поля только для чтения или постоянного поля похоже на объединение Закона Непредвиденных Последствий с Законом Мерфи .

Powerlord
источник
1
ответ - «просто любопытство», как упоминалось выше.
Рон Кляйн
Бывают моменты, когда мне приходится проделывать именно этот трюк, чтобы написать лучший код, который я могу. Дело в точке - elegantcode.com/2008/04/17/testing-a-membership-provider
искроуловитель
3
Я тоже использую этот трюк в проектах модульного тестирования, чтобы переопределить значения по умолчанию, которые не следует изменять ни в каком бизнес-коде ...
Коэн
И я пытался установить частные внутренние свойства в библиотеках базовых классов для целей тестирования, в частности в API членства, где MS пометила все как частное, внутреннее и без установщиков свойств. Для этого есть случаи, но вы правы, если вопрос относится к API, находящемуся под вашим контролем
Чад Грант
3
Бывают ситуации, когда это имеет смысл. Мапперы O / R, такие как NHibernate, делают это постоянно для гидратации, потому что это единственный способ реализовать инкапсуляцию данных для постоянных сущностей в первую очередь.
Крис
2

Не делай этого.

Я просто потратил день на исправление сюрреалистической ошибки, из-за которой объекты могли не относиться к их собственному объявленному типу.

Изменение поля только для чтения сработало один раз. Но если вы попытаетесь изменить его снова, вы получите такие ситуации:

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

Так что не делай этого.

Это было в среде выполнения Mono (игровой движок Unity).

Тайнан Сильвестр
источник
2
К вашему сведению. Unity Engine не может быть использован для эффективного ответа на такие глубокие вопросы, связанные с языком C #, как этот, потому что Unity выполняет свою собственную компиляцию C #, как если бы .cs были в некотором смысле сценариями. Я не говорю, что ваша точка зрения неверна, но она определенно специфична для Unity Engine и C # вместе.
Дэйв Джеллисон
0

Я просто хочу добавить, что если вам нужно сделать это для модульного тестирования, вы можете использовать:

A) PrivateObject класс

Б) Вам по-прежнему понадобится экземпляр PrivateObject, но вы можете сгенерировать объекты Accessor с помощью Visual Studio. Как: восстановить частные аксессоры

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

Dudeman3000
источник