我可以使用反射更改C#中的私有只读字段吗?

Can I change a private readonly field in C# using reflection?

我想知道,由于很多事情都可以通过反射来完成,在构造函数完成执行之后,我可以更改一个私有的只读字段吗?(注:只是好奇)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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

你可以:

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


显而易见的是尝试一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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有不同的规则,有趣的是,您必须显式地设置EDCOX1 0),以便于访问,并且它只适用于实例字段。)


我同意其他的答案,因为它通常是有效的,特别是E.Lippert的评论,这不是文档化的行为,因此不是未来的证明代码。

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

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


你问为什么要像那样破坏封装。

我使用实体助手类来水合物实体。这将使用反射获取新空实体的所有属性,并将属性/字段名与结果集中的列匹配,并使用propertyinfo.setvalue()设置它。

我不希望其他任何人能够改变这个值,但我也不想全力以赴为每个实体定制代码水合方法。

我的许多存储过程返回的结果集与表或视图不直接对应,因此代码生成ORM对我没有任何作用。


另一种简单的方法是使用不安全的(或者您可以通过dllimport将字段传递给C方法并将其设置在那里)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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);
        }
    }
}

不要这样做。

我刚刚花了一天时间修复了一个超现实的bug,对象不能是它们自己声明的类型。

修改只读字段工作一次。但是如果你再次尝试修改它,你会得到这样的情况:

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

所以不要这样做。

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


答案是肯定的,但更重要的是:

你为什么要这么做?故意破坏封装对我来说似乎是一个非常糟糕的主意。

使用反射来改变一个只读或常量字段,就像是将意想不到的结果定律与墨菲定律结合在一起。


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

a)privateObject类

b)您仍然需要privateObject实例,但可以使用Visual Studio生成"accessor"对象。如何:重新生成专用访问器

如果您在单元测试之外设置代码中某个对象的私有字段,那么这就是"代码气味"的一个实例,我认为您可能希望这样做的唯一其他原因是,如果您正在处理第三方库,并且不能更改目标类代码。即便如此,您可能还是希望联系第三方,解释您的情况,看看他们是否会继续进行,并更改他们的代码以满足您的需要。