我可以使用反射更改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

Answers:


151

您可以:

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

2
你当然是对的。我很抱歉。是的,我确实尝试过,但是我尝试直接设置一个只读属性,而不使用后备字段。我的尝试没有任何意义。您的解决方案工作正常(这次再次测试,正确无误)
Sage Pourpre

我们如何用起订量做到这一点?
l --''''''---------''''''''''''

在dotnet core 3.0中,这不再可能。引发System.FieldAccessException,说:“类型'Foo'初始化后,不能设置initonly静态字段'bar'。”
David Perfors,

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
艾哈迈德(Ahmed)-但是我们没有使用语言来做,所以语言规范没有得到投票...
马克·

4
是的-有很多事情可以“打破”该语言的要求。例如,您可以多次运行类型初始化器。
乔恩·斯基特

28
我还注意到,仅仅因为您今天可以在某些实现中使用,并不意味着您可以在所有时间上都可以使用。我不知道我们在哪里记录了只读字段必须通过反射可变的地方。据我所知,符合CLI的实现完全可以自由地实现只读字段,以便在构造函数完成后通过反射进行更改时它们会引发异常。
埃里克·利珀特

3
但这并不是很好,因为在某些情况下,我需要扩展的类比最初设计的要多。如果计划周全,应该总有一种方法可以覆盖封装。唯一的选择是血腥的,有时如果不重写框架的一部分就无法实现。
漂流者2010年

5
@drifter:那时,您正在向痛苦的世界敞开大门。您所依赖的是当前的实现细节,这些细节可以在将来的版本中轻松更改。
乔恩·斯基特

11

我同意其他答案,因为它通常可以正常工作,尤其是E. Lippert的评论,即这不是书面行为,因此也不是面向未来的代码。

但是,我们也注意到了另一个问题。如果在权限受限的环境中运行代码,则可能会出现异常。

我们刚遇到一个案例,我们的代码在我们的机器上可以正常工作,但是VerificationException当代码在受限环境中运行时,我们收到了一个提示。罪魁祸首是对只读字段的setter的反射调用。当我们删除该字段的只读限制时,它起作用了。


2
可能想知道哪些环境会引发VerificationException
Sergey Zhukov

4

您问为什么要破坏这种封装。

我使用一个实体助手类来充实实体。这使用反射来获取新的空实体的所有属性,并将属性/字段名称与结果集中的列匹配,并使用propertyinfo.setvalue()对其进行设置。

我不希望其他任何人都能更改该值,但是我也不希望为每个实体都自定义代码混合方法。

我的许多存储过程都返回的结果集与表或视图不直接对应,因此ORM代码对我无济于事。


1
我还使用它来克服一些api限制,在这些限制中,值要么是硬编码的,要么需要我无法提供的配置文件。(例如,当通过反射加载程序集时,DIME附件的WSE 2.0文件大小)
StingyJack 2010年

3

使用不安全方法进行此操作的另一种简单方法(或者您可以通过DLLImport将字段传递给C方法并在此处进行设置)。

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 2010年

3
我也在单元测试项目中使用此技巧来覆盖默认值,该默认值不应在任何业务代码中更改...
Koen 2010年

我试图在基类库中设置私有内部属性以进行测试,特别是Membership API,其中MS将所有内容标记为私有,内部且不设置属性。有此情况,但你是正确的,如果应用问题的API你的控制之下
乍得格兰特

3
在某些情况下,这是有道理的。像NHibernate这样的O / R映射器会一直进行水合作用,因为这是您首先可以为持久性实体实现数据封装的唯一方法。
克里斯,2012年

2

不要这样

我只花了一天的时间修复一个超现实的错误,该错误可能导致对象可能不是自己声明的类型。

修改只读字段一次。但是,如果您尝试再次对其进行修改,则会遇到以下情况:

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

所以不要这样做。

这是在Mono运行时(Unity游戏引擎)上进行的。


2
仅供参考-从某种意义上说,Unity引擎无法用于有效回答特定于C#语言的深层问题,因为从某种意义上讲,Unity会执行自己的C#编译,就好像.cs是脚本一样。我并不是说您的观点无效,但这肯定是特定于Unity Engine和C#的。
戴夫·杰里森

0

我只想补充一点,如果您需要为单元测试做这些事情,那么可以使用:

A)PrivateObject

B)您仍然需要一个PrivateObject实例,但是您可以使用Visual Studio生成“访问器”对象。 如何:重新生成专用访问器

如果您要在单元测试之外的代码中设置对象的私有字段,那将是“代码异味”的一个实例,我想您可能要这样做的唯一其他原因是与第三方打交道库,您不能更改目标类代码。即使那样,您可能仍想与第三方联系,说明您的情况,看看他们是否会继续进行并更改代码以适应您的需求。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.