ฉันสามารถเปลี่ยนฟิลด์แบบอ่านอย่างเดียวส่วนตัวใน 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

เราจะทำอย่างไรกับ moq?
l --''''''--------- '' '' '' ''

ใน dotnet core 3.0 ไม่สามารถทำได้อีกต่อไป System.FieldAccessException ถูกโยนว่า: "ไม่สามารถตั้งค่าเริ่มต้นฟิลด์คงที่ 'bar' หลังจากประเภท 'Foo' เริ่มต้นแล้ว
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 - แต่เราไม่ได้ใช้ภาษาในการทำดังนั้น spec ภาษาจึงไม่ได้รับการโหวต ...
Marc Gravell

4
ใช่ - มีหลายสิ่งที่สามารถทำได้ซึ่ง "แบ่ง" สิ่งที่ภาษาต้องการ คุณสามารถเรียกใช้ประเภท initializers ได้หลายครั้งเช่น
Jon Skeet

28
ฉันทราบด้วยว่าการที่คุณสามารถใช้งานบางอย่างได้ในปัจจุบันไม่ได้หมายความว่าคุณจะสามารถใช้งานได้ตลอดเวลา ฉันไม่ทราบว่าสถานที่ใดที่เราจัดทำเอกสารว่าเขตข้อมูลแบบอ่านอย่างเดียวจะต้องเปลี่ยนแปลงได้โดยการสะท้อนกลับ เท่าที่ฉันรู้การใช้งาน CLI ที่สอดคล้องกันนั้นมีอิสระอย่างสมบูรณ์ในการใช้ฟิลด์แบบอ่านอย่างเดียวซึ่งจะทำให้เกิดข้อยกเว้นเมื่อกลายพันธุ์ผ่านการสะท้อนหลังจากตัวสร้างเสร็จสิ้น
Eric Lippert

3
นั่นไม่ใช่เรื่องดีเพราะมีหลายกรณีที่ฉันต้องขยายชั้นเรียนมากกว่าที่ออกแบบไว้ในตอนแรก ควรมีวิธีลบล้างการห่อหุ้มเสมอเมื่อมีการวางแผนอย่างดี ทางเลือกเดียวคือเลือดและบางครั้งก็ไม่สามารถทำได้หากไม่มีการเขียนส่วนของกรอบ
drifter

5
@drifter: ณ จุดนั้นคุณกำลังเปิดตัวเองสู่โลกแห่งความเจ็บปวด คุณอาศัยรายละเอียดการใช้งานปัจจุบันซึ่งสามารถเปลี่ยนแปลงได้อย่างง่ายดายในเวอร์ชันอนาคต
Jon Skeet

11

ฉันเห็นด้วยกับคำตอบอื่น ๆ ที่ใช้งานได้โดยทั่วไปและโดยเฉพาะอย่างยิ่งกับความคิดเห็นของ E.Lippert ว่านี่ไม่ใช่พฤติกรรมที่มีการบันทึกไว้ดังนั้นจึงไม่ใช่รหัสที่พิสูจน์ได้ในอนาคต

อย่างไรก็ตามเรายังสังเกตเห็นอีกประเด็นหนึ่ง หากคุณกำลังเรียกใช้รหัสของคุณในสภาพแวดล้อมที่มีสิทธิ์ จำกัด คุณอาจได้รับข้อยกเว้น

เราเพิ่งมีกรณีที่รหัสของเราทำงานได้ดีบนเครื่องของเรา แต่เราได้รับVerificationExceptionเมื่อโค้ดทำงานในสภาพแวดล้อมที่ จำกัด ผู้ร้ายคือการเรียกร้องสะท้อนไปยังผู้กำหนดเขตข้อมูลแบบอ่านอย่างเดียว ได้ผลเมื่อเราลบข้อ จำกัด แบบอ่านอย่างเดียวของฟิลด์นั้น


2
สนใจที่จะทราบว่าสภาพแวดล้อมใดที่ส่ง VerificationException
Sergey Zhukov

4

คุณถามว่าทำไมคุณถึงต้องการทำลายสิ่งห่อหุ้มแบบนั้น

ฉันใช้คลาสผู้ช่วยเอนทิตีเพื่อให้เอนทิตี สิ่งนี้ใช้การสะท้อนเพื่อรับคุณสมบัติทั้งหมดของเอนทิตีว่างใหม่และจับคู่ชื่อคุณสมบัติ / ฟิลด์กับคอลัมน์ในชุดผลลัพธ์และตั้งค่าโดยใช้ propertyinfo.setvalue ()

ฉันไม่ต้องการให้ใครสามารถเปลี่ยนค่าได้ แต่ฉันไม่ต้องการใช้ความพยายามทั้งหมดในการกำหนดวิธีการไฮเดรชันโค้ดที่กำหนดเองสำหรับทุกเอนทิตีเช่นกัน

procs ที่เก็บไว้จำนวนมากของฉันส่งคืนผลลัพธ์ที่ไม่ตรงกับตารางหรือมุมมองโดยตรงดังนั้นรหัส gen ORM จึงไม่ทำอะไรให้ฉัน


1
ฉันยังใช้มันเพื่อก้าวข้ามข้อ จำกัด ของ api ที่ค่าเป็นฮาร์ดโค้ดหรือต้องการไฟล์กำหนดค่าที่ฉันไม่สามารถให้ได้ (ขนาดไฟล์ WSE 2.0 สำหรับไฟล์แนบ DIME เมื่อแอสเซมบลีถูกโหลดผ่านการสะท้อนกลับเป็นต้น)
StingyJack

3

อีกวิธีง่ายๆในการทำเช่นนี้โดยใช้ไม่ปลอดภัย (หรือคุณสามารถส่งฟิลด์ไปยังวิธีการ C ผ่าน DLLImport และตั้งค่าที่นั่น)

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

3
ฉันก็ใช้เคล็ดลับนี้ในโครงการทดสอบหน่วยเพื่อแทนที่ค่าเริ่มต้นซึ่งไม่ควรเปลี่ยนแปลงในรหัสธุรกิจใด ๆ ...
Koen

และฉันกำลังพยายามตั้งค่าคุณสมบัติภายในส่วนตัวในไลบรารีคลาสพื้นฐานเพื่อวัตถุประสงค์ในการทดสอบโดยเฉพาะ Membership API ที่ MS ทำเครื่องหมายทุกอย่างเป็นส่วนตัวภายในและไม่มีตัวตั้งค่าในคุณสมบัติ มีหลายกรณีสำหรับสิ่งนี้ แต่คุณถูกต้องหากคำถามใช้กับ API ภายใต้การควบคุมของคุณ
Chad Grant

3
มีสถานการณ์ที่เหมาะสม ตัวทำแผนที่ O / R เช่น NHibernate ทำสิ่งนี้ตลอดเวลาเพื่อการชุ่มชื้นเนื่องจากนี่เป็นวิธีเดียวที่คุณสามารถใช้การห่อหุ้มข้อมูลสำหรับเอนทิตีถาวรได้ตั้งแต่แรก
chris

2

อย่าทำแบบนี้

ฉันใช้เวลาเพียงวันเดียวในการแก้ไขข้อบกพร่องเหนือจริงซึ่งวัตถุอาจไม่ใช่ประเภทที่ประกาศไว้

การแก้ไขฟิลด์แบบอ่านอย่างเดียวใช้งานได้ครั้งเดียว แต่ถ้าคุณพยายามแก้ไขอีกครั้งคุณจะได้รับสถานการณ์เช่นนี้:

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

ดังนั้นอย่าทำ

นี่คือบนรันไทม์ Mono (เอ็นจิ้นเกม Unity)


2
FYI - Unity Engine ไม่สามารถใช้เพื่อตอบคำถามเฉพาะภาษา C # แบบเจาะลึกได้อย่างมีประสิทธิภาพเช่นนี้เนื่องจาก Unity ดำเนินการรวบรวม C # ของตัวเองราวกับว่า. cs เป็นสคริปต์ในแง่หนึ่ง ฉันไม่ได้บอกว่าประเด็นของคุณไม่ถูกต้อง แต่แน่นอนว่าเฉพาะกับ Unity Engine & C # ด้วยกัน
Dave Jellison

0

ฉันแค่ต้องการเพิ่มว่าหากคุณต้องการทำสิ่งนี้สำหรับการทดสอบหน่วยคุณสามารถใช้:

A) คลาสPrivateObject

B) คุณยังต้องมีอินสแตนซ์ PrivateObject แต่คุณสามารถสร้างอ็อบเจกต์ "Accessor" ด้วย Visual Studio ได้ วิธีการ: สร้างผู้เข้าถึงส่วนตัวใหม่

หากคุณกำลังตั้งค่าฟิลด์ส่วนตัวของวัตถุในโค้ดของคุณนอกการทดสอบหน่วยนั่นอาจเป็นตัวอย่างของ "กลิ่นรหัส" ฉันคิดว่าอาจเป็นเหตุผลเดียวที่คุณต้องการทำเช่นนี้คือหากคุณกำลังติดต่อกับบุคคลที่สาม ไลบรารีและคุณไม่สามารถเปลี่ยนรหัสคลาสเป้าหมายได้ ถึงอย่างนั้นคุณอาจต้องการติดต่อบุคคลที่สามอธิบายสถานการณ์ของคุณและดูว่าพวกเขาจะไม่ดำเนินการต่อหรือไม่และเปลี่ยนรหัสเพื่อรองรับความต้องการของคุณ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.