リフレクションを使用して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 2015

どうすればmoqでこれを行うことができますか?
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
アーメド-しかし、言語を使用していないので、言語仕様は採決されません...
Marc Gravell

4
うん-言語が望むものを「壊す」ことができることがたくさんあります。たとえば、型初期化子を複数回実行できます。
Jon Skeet、

28
また、今日一部の実装で実行できるからといって、すべての実装でいつでも実行できるとは限らないことにも注意してください。読み取り専用フィールドがリフレクションを介して変更可能でなければならないことを文書化する場所を知りません。私の知る限り、CLIの準拠実装は、コンストラクターの完了後にリフレクションを介して変更されたときに例外をスローするように、読み取り専用フィールドを完全に自由に実装できます。
Eric Lippert、

3
ただし、本来の設計よりもクラスを拡張する必要がある場合があるため、これは良くありません。適切に計画されている場合、カプセル化をオーバーライドする方法が常に必要です。唯一の代替策は厄介で、フレームワークの一部を書き直さないとできない場合もあります。
ドリフタ

5
@drifter:その時点で、あなたは苦痛の世界に自分を開放しています。あなたは将来のバージョンで簡単に変更できる現在の実装の詳細に依存しています。
Jon Skeet、2010

11

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

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

コードがマシン上で正常に機能するケースがありVerificationExceptionましたが、制限された環境でコードが実行されたときにaを受け取りました。犯人は、読み取り専用フィールドの設定者へのリフレクションコールでした。そのフィールドの読み取り専用の制限を削除したときに機能しました。


2
VerificationExceptionを投げている環境を知るために興味がある
セルゲイジューコフ

4

なぜそのようなカプセル化を解除したいのかと尋ねました。

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

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

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


1
また、値がハードコードされているか、提供できない構成ファイルが必要なAPIの制限を回避するためにも使用しました。(たとえば、アセンブリがリフレクションを介してロードされた場合のDIME添付ファイルのWSE 2.0ファイルサイズ)
StingyJack

3

unsafeを使用してこれを行う別の簡単な方法(または、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
その答えは、前述のように「ただの好奇心」です。
ロンクライン

自分ができる最高のコードを書くために、このトリックだけをしなければならないときがあります。適切な
火花

3
私もこのトリックをユニットテストプロジェクトで使用して、どのビジネスコードでも変更してはならないデフォルト値を上書きしています...
Koen

そして、私はテスト目的で基本クラスライブラリのプライベート内部プロパティ、特にメンバーシップAPIを設定しようとしました。MSはすべてのプロパティをプライベート、内部、セッターなしでマークしました。そここのため例がありますが、質問はあなたのコントロール下でAPIに適用された場合、あなたは正しいです
チャドグラント

3
それが理にかなっている状況があります。NHibernateのようなO / Rマッパーは常にハイドレーションのためにこれを行います。これは、最初に永続エンティティのデータカプセル化を実装できる唯一の方法だからです。
クリス

2

これを行わないでください。

私は、オブジェクトが独自に宣言されたタイプではない可能性がある超現実的なバグを修正するために1日過ごしました。

読み取り専用フィールドの変更は1回しか機能しませんでした。しかし、もう一度変更しようとすると、次のような状況になります。

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

だからそれをしないでください。

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


2
参考までに-Unityエンジンは、ある意味で.csがスクリプトであるかのように、UnityがC#の独自のコンパイルを実行するため、このような深いC#言語固有の質問に効果的に回答するために使用できません。あなたの主張が無効であると言っているわけではありませんが、それは確かにUnityエンジンとC#の両方に固有のものです。
Dave Jellison

0

単体テストのためにこれを行う必要がある場合は、次のように追加できます。

A)PrivateObjectクラス

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

ユニットテスト以外のコードでオブジェクトのプライベートフィールドを設定する場合、それは「コードのにおい」のインスタンスになります。おそらく、これを実行する他の唯一の理由は、サードパーティを扱っている場合だと思いますライブラリを使用して、ターゲットクラスコードを変更することはできません。それでも、おそらくサードパーティに連絡して、状況を説明し、彼らが先に進まないかどうかを確認し、必要に応じてコードを変更する必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.