web-dev-qa-db-ja.com

リフレクションを使用してC#のプライベート読み取り専用フィールドを変更できますか?

リフレクションを使用して多くのことができるため、コンストラクターの実行完了後にプライベート読み取り専用フィールドを変更できますか?
(注:単なる好奇心)

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
110
Ron Klein

あなたはできる:

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

明白なことは、それを試してみることです:

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をアクセス可能に明示的に設定する必要があります。とにかくインスタンスフィールドに対してのみ機能します。)

52
Jon Skeet

一般的にが機能するという点で他の答えに同意します。特に、これは文書化された動作ではないため、将来を保証するコードではないというE. Lippertのコメントに同意します。

ただし、別の問題にも気付きました。権限が制限された環境でコードを実行している場合、例外が発生する可能性があります。

マシンでコードが正常に機能する場合がありましたが、コードが制限された環境で実行されたときにVerificationExceptionを受け取りました。原因は、読み取り専用フィールドのセッターへのリフレクションコールでした。そのフィールドの読み取り専用制限を削除したときに機能しました。

12
Andreas

そのようなカプセル化を解除する理由を尋ねました。

エンティティヘルパークラスを使用して、エンティティをハイドレートします。これはリフレクションを使用して新しい空のエンティティのすべてのプロパティを取得し、プロパティ/フィールド名を結果セットの列に一致させ、propertyinfo.setvalue()を使用して設定します。

他の誰かが値を変更できるようにしたくありませんが、すべてのエンティティのカスタムコードハイドレーションメソッドに全力を尽くしたくはありません。

ストアドプロシージャの多くは、テーブルまたはビューに直接対応しない結果セットを返すため、コード生成ORMは何もしません。

4
Necroposter

答えはイエスですが、より重要なことは次のとおりです。

なぜしたいのですか?意図的にカプセル化を破ることは、恐ろしく悪い考えのように思えます。

リフレクションを使用して読み取り専用フィールドまたは定数フィールドを変更することは、 意図しない結果の法則マーフィーの法則 を組み合わせるようなものです。

2
Powerlord

これをしないでください。

オブジェクトが独自の宣言された型ではない可能性のあるシュールなバグを修正するのに1日を費やしました。

読み取り専用フィールドの変更は一度だけでした。ただし、再度変更しようとすると、次のような状況になります。

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

だからしないでください。

これはMonoランタイム(Unityゲームエンジン)にありました。

2
Tynan Sylvester

安全でないを使用してこれを行う別の簡単な方法(または、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
zezba9000

ユニットテストのためにこのようなことをする必要がある場合、それを追加したいだけです:

A) PrivateObject クラス

B)PrivateObjectインスタンスは引き続き必要ですが、Visual Studioで「アクセサー」オブジェクトを生成できます。 方法:プライベートアクセサーを再生成する

単体テスト以外のコードでオブジェクトのプライベートフィールドを設定している場合、それは「コードの匂い」のインスタンスになります。ライブラリおよびターゲットクラスコードを変更することはできません。それでも、サードパーティに連絡し、状況を説明し、彼らが先に進まないかどうかを確認し、必要に応じてコードを変更することをお勧めします。

0
Dudeman3000