리플렉션을 사용하여 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
물론 당신은 절대적으로 옳습니다. 죄송합니다. 그리고 예, 시도했지만 백업 필드를 사용하지 않고 읽기 전용 속성을 직접 설정하려고 시도했습니다. 내가 시도한 것은 의미가 없습니다. 귀하의 솔루션은 완벽하게 정상적으로 (다시 테스트, 올바르게 시간) 작동
세이지 Pourpre

moq로 어떻게 할 수 있습니까?
l --''''''--------- '' '' '' '' '' ''

dotnet core 3.0에서는 더 이상 가능하지 않습니다. "Foo '유형이 초기화 된 후 initonly 정적 필드'bar '를 설정할 수 없습니다."라는 System.FieldAccessException이 발생합니다.
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);
    }        
}

이것은 잘 작동합니다. (자바에는 흥미롭게도 다른 규칙이 있습니다. 명시 적 Field으로 액세스 가능하도록 설정해야 하며 어쨌든 인스턴스 필드에서만 작동합니다.)


4
Ahmed-하지만 우리는 언어를 사용하지 않아서 언어 사양이 투표를받지 못합니다 ...
Marc Gravell

4
네-언어가 원하는 것을 "파괴"하는 많은 일이 있습니다. 예를 들어 형식 이니셜 라이저를 여러 번 실행할 수 있습니다.
Jon Skeet

28
또한 오늘날 일부 구현에서 가능하다고해서 모든 구현에서 항상 가능하다는 의미는 아닙니다. 읽기 전용 필드가 리플렉션을 통해 변경 가능해야한다는 것을 문서화하는 곳을 알지 못합니다. 내가 아는 한, CLI의 준수 구현은 생성자가 완료된 후 리플렉션을 통해 변경 될 때 예외가 발생하도록 읽기 전용 필드를 구현하는 데 완벽하게 자유 롭습니다.
Eric Lippert

3
하지만 원래 설계된 것보다 더 많은 클래스를 확장해야하는 경우가 있기 때문에 좋지 않습니다. 잘 계획된 경우 캡슐화를 재정의하는 방법이 항상 있어야합니다. 유일한 대안은 피투성이이며 때로는 프레임 워크의 일부를 다시 작성하지 않고는 불가능합니다.
표류자 dec

5
@drifter : 그 시점에서 당신은 고통의 세계에 자신을 개방하고 있습니다. 향후 버전에서 쉽게 변경 될 수있는 현재 구현 세부 정보에 의존하고 있습니다.
Jon Skeet

11

나는 그것이 일반적으로 작동한다는 점에서 특히 E. Lippert가 문서화 된 행동이 아니므로 미래 보장 코드가 아니라는 다른 답변에 동의 합니다.

그러나 우리는 또 다른 문제를 발견했습니다. 제한된 권한이있는 환경에서 코드를 실행하는 경우 예외가 발생할 수 있습니다.

우리의 코드가 컴퓨터에서 제대로 작동하는 경우가 있었지만 VerificationException코드가 제한된 환경에서 실행되었을 때 오류를 받았습니다 . 범인은 읽기 전용 필드의 setter에 대한 리플렉션 호출이었습니다. 해당 필드의 읽기 전용 제한을 제거했을 때 작동했습니다.


2
대한 VerificationException 던져있는 환경을 알고 관심을 가질만한
세르게이 주코프

4

당신은 왜 그렇게 캡슐화를 깨고 싶은지 물었습니다.

엔티티 헬퍼 클래스를 사용하여 엔티티를 수화합니다. 이것은 리플렉션을 사용하여 비어있는 새 엔터티의 모든 속성을 가져오고 속성 / 필드 이름을 결과 집합의 열과 일치시키고 propertyinfo.setvalue ()를 사용하여 설정합니다.

다른 사람이 값을 변경할 수 없도록하고 싶지만 모든 엔터티에 대한 사용자 지정 코드 수화 방법에 모든 노력을 기울이고 싶지는 않습니다.

내 저장된 procs 중 다수는 테이블이나 뷰에 직접 해당하지 않는 결과 집합을 반환하므로 코드 생성 ORM은 나에게 아무 일도하지 않습니다.


1
또한 값이 하드 코딩되었거나 제공 할 수없는 구성 파일이 필요한 일부 API 제한을 극복하기 위해 사용했습니다. (예를 들어 어셈블리가 리플렉션을 통해로드되었을 때 DIME 첨부 파일의 WSE 2.0 파일 크기)
StingyJack

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
대답은 위에서 언급했듯이 "단지 호기심"입니다.
Ron Klein

내가 할 수있는 최고의 코드를 작성하기 위해이 트릭을해야 할 때가 있습니다. 사례 -elegantcode.com/2008/04/17/testing-a-membership-provider
sparker 2010

3
나도 단위 테스트 프로젝트에서이 트릭을 사용하여 비즈니스 코드에서 변경해서는 안되는 기본값을 재정의합니다 ...
Koen

그리고 테스트 목적으로 기본 클래스 라이브러리, 특히 Membership API에서 개인 내부 속성을 설정하려고 시도했습니다. 여기서 MS는 모든 것을 개인, 내부 및 속성에 대한 setter없이 표시했습니다. 이 이것에 대한 경우가 있지만, 문제는 사용자가 제어하는 API에 적용되는 경우가 올바른지
차드 그랜트

3
말이되는 상황이 있습니다. NHibernate와 같은 O / R 매퍼는 항상 수화를 위해이 작업을 수행합니다. 이것이 처음에 영구 엔티티에 대한 데이터 캡슐화를 구현할 수있는 유일한 방법이기 때문입니다.
chris

2

이러지마

객체가 자체적으로 선언 된 유형이 아닐 수있는 초현실적 인 버그를 수정하는 데 하루를 보냈습니다.

읽기 전용 필드 수정은 한 번 작동했습니다. 그러나 다시 수정하려고하면 다음과 같은 상황이 발생합니다.

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

그러니하지 마세요.

이것은 Mono 런타임 (Unity 게임 엔진)에있었습니다.


2
참고로 Unity 엔진은 .cs가 스크립트 인 것처럼 자체적으로 C # 컴파일을 수행하기 때문에 이와 같은 심층 C # 언어 관련 질문에 효과적으로 대답하는 데 사용할 수 없습니다. 귀하의 요점이 유효하지 않다고 말하는 것은 아니지만 확실히 Unity 엔진과 C #에 한정되어 있습니다.
Dave Jellison

0

단위 테스트를 위해이 작업을 수행해야하는 경우 다음을 사용할 수 있습니다.

A) PrivateObject 클래스

B) 여전히 PrivateObject 인스턴스가 필요하지만 Visual Studio를 사용하여 "접근 자"개체를 생성 할 수 있습니다. 방법 : 개인 접근 자 재생성

단위 테스트 외부에서 코드에서 개체의 개인 필드를 설정하는 경우 "코드 냄새"의 인스턴스가 될 것입니다.이 작업을 수행하려는 유일한 다른 이유는 타사와 거래하는 경우 일 것입니다. 라이브러리 및 대상 클래스 코드를 변경할 수 없습니다. 그럼에도 불구하고 제 3 자에게 연락하여 상황을 설명하고 그들이 귀하의 필요에 맞게 코드를 변경하지 않는지 확인하고 싶을 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.