Tôi có thể thay đổi trường chỉ đọc riêng tư trong C # bằng cách sử dụng phản chiếu không?


115

Tôi đang tự hỏi, vì rất nhiều thứ có thể được thực hiện bằng cách sử dụng phản chiếu, tôi có thể thay đổi trường riêng tư chỉ đọc sau khi phương thức khởi tạo hoàn thành việc thực thi nó không?
(lưu ý: chỉ là tò mò)

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

Câu trả lời:


151

Bạn có thể:

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

2
Bạn hoàn toàn đúng, tất nhiên. Lời xin lỗi của tôi. Và có, tôi đã thử, nhưng tôi đã cố gắng đặt thuộc tính chỉ đọc trực tiếp, không sử dụng trường hỗ trợ. Những gì tôi đã cố gắng không có ý nghĩa. Giải pháp của bạn hoạt động hoàn toàn tốt đẹp (thử nghiệm một lần nữa, một cách chính xác thời gian này)
Sage Pourpre

làm thế nào chúng ta có thể làm điều này với moq?
l --''''''--------- '' '' '' '' '' ''

Trong dotnet core 3.0, điều này không còn nữa. Một System.FieldAccessException được ném ra với nội dung: "Không thể đặt thanh" trường tĩnh "initonly sau khi loại 'Foo' được khởi tạo."
David Perfors

54

Điều hiển nhiên là hãy thử nó:

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);
    }        
}

Điều này hoạt động tốt. (Điều thú vị là Java có các quy tắc khác nhau - bạn phải đặt rõ ràng Fieldlà có thể truy cập được và nó sẽ chỉ hoạt động đối với các trường ví dụ.)


4
Ahmed - nhưng chúng tôi không sử dụng ngôn ngữ để làm điều đó, vì vậy thông số ngôn ngữ không nhận được phiếu bầu ...
Marc Gravell

4
Đúng vậy - có rất nhiều thứ có thể được thực hiện mà "phá vỡ" những gì ngôn ngữ muốn. Ví dụ, bạn có thể chạy trình khởi tạo kiểu nhiều lần.
Jon Skeet

28
Tôi cũng lưu ý rằng chỉ vì bạn có thể trong một số triển khai ngày hôm nay không có nghĩa là bạn có thể thực hiện mọi lúc. Tôi không biết bất kỳ nơi nào mà chúng tôi ghi lại rằng các trường chỉ đọc phải có thể thay đổi thông qua phản chiếu. Theo như tôi biết, việc triển khai tuân thủ CLI là hoàn toàn miễn phí để triển khai các trường chỉ đọc để chúng ném ra các ngoại lệ khi bị thay đổi thông qua phản chiếu sau khi phương thức khởi tạo được thực hiện.
Eric Lippert

3
Tuy nhiên, điều đó là không tốt, vì có những trường hợp tôi cần mở rộng một lớp nhiều hơn so với thiết kế ban đầu. Luôn luôn phải có một cách để ghi đè đóng gói khi nó được lên kế hoạch tốt. Các lựa chọn thay thế duy nhất là đẫm máu và đôi khi không thể thực hiện được nếu không viết lại một phần của khuôn khổ.
Drifter

5
@drifter: Tại thời điểm đó, bạn đang mở ra cho mình một thế giới đau đớn. Bạn đang dựa vào chi tiết triển khai hiện tại có thể dễ dàng thay đổi trong phiên bản trong tương lai.
Jon Skeet

11

Tôi đồng ý với các câu trả lời khác ở chỗ nó hoạt động chung và đặc biệt với nhận xét của E. Lippert rằng đây không phải là hành vi được lập thành văn bản và do đó không phải là mã kiểm chứng trong tương lai.

Tuy nhiên, chúng tôi cũng nhận thấy một vấn đề khác. Nếu bạn đang chạy mã của mình trong môi trường có các quyền hạn chế, bạn có thể nhận được một ngoại lệ.

Chúng tôi vừa gặp trường hợp mã của chúng tôi hoạt động tốt trên máy của chúng tôi, nhưng chúng tôi đã nhận được lỗi VerificationExceptionkhi mã chạy trong một môi trường hạn chế. Thủ phạm là một cuộc gọi phản chiếu tới bộ thiết lập của một trường chỉ đọc. Nó hoạt động khi chúng tôi loại bỏ giới hạn chỉ đọc của trường đó.


2
Sẽ được quan tâm để biết được môi trường ném VerificationException
Sergey Zhukov

4

Bạn đã hỏi tại sao bạn lại muốn phá vỡ sự đóng gói như vậy.

Tôi sử dụng một lớp trợ giúp thực thể để hydrat thực thể. Điều này sử dụng sự phản chiếu để nhận tất cả các thuộc tính của một thực thể trống mới và khớp tên thuộc tính / trường với cột trong tập kết quả và đặt nó bằng cách sử dụng propertyinfo.setvalue ().

Tôi không muốn bất kỳ ai khác có thể thay đổi giá trị, nhưng tôi cũng không muốn mất tất cả nỗ lực để tùy chỉnh các phương pháp hydrat hóa mã cho mọi thực thể.

Nhiều procs được lưu trữ của tôi trả về các tập kết quả không tương ứng trực tiếp với các bảng hoặc dạng xem, vì vậy gen mã ORM không làm gì cho tôi.


1
Tôi cũng đã sử dụng nó để vượt qua một số hạn chế của api trong đó giá trị được mã hóa cứng hoặc yêu cầu tệp cấu hình mà tôi không thể cung cấp. (WSE 2.0 kích thước tập tin cho file đính kèm DIME khi các hội đồng được nạp thông qua phản ánh, ví dụ)
StingyJack

3

Một cách đơn giản khác để thực hiện việc này bằng cách sử dụng không an toàn (hoặc bạn có thể chuyển trường sang phương thức C thông qua DLLImport và đặt nó ở đó).

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

Câu trả lời là có, nhưng quan trọng hơn:

Tại sao bạn sẽ muốn? Cố ý phá vỡ sự đóng gói dường như là một ý tưởng tồi tệ đối với tôi.

Sử dụng phản xạ để thay đổi một trường chỉ đọc hoặc không đổi cũng giống như việc kết hợp Luật Hậu quả không mong muốn với Định luật Murphy .


1
câu trả lời là "chỉ là sự tò mò", như đã nói ở trên.
Ron Klein

Đôi khi tôi thấy mình phải làm thủ thuật này để viết mã tốt nhất mà tôi có thể. Trường hợp điển hình - Elegantcode.com/2008/04/17/testing-a-membership-provider
sparker 12/04/10

3
Tôi cũng sử dụng thủ thuật này trong đơn vị thử nghiệm dự án để ghi đè giá trị mặc định mà không nên thay đổi trong bất kỳ mã số kinh doanh ...
Koen

Và tôi đã cố gắng thiết lập các thuộc tính nội bộ riêng tư trong các thư viện lớp cơ sở cho mục đích thử nghiệm, cụ thể là API thành viên, nơi MS đánh dấu mọi thứ là riêng tư, nội bộ và không có bộ cài đặt trên thuộc tính. Có những trường hợp cho điều này, nhưng bạn là chính xác nếu câu hỏi áp dụng cho một API dưới sự kiểm soát của bạn
Chad Grant

3
Có những tình huống mà nó có ý nghĩa. Các trình lập bản đồ O / R như NHibernate luôn làm điều đó cho quá trình hydrat hóa, bởi vì đây là cách duy nhất bạn có thể triển khai tính năng đóng gói dữ liệu cho các thực thể liên tục ngay từ đầu.
chris

2

Đừng làm điều này.

Tôi vừa dành một ngày để sửa một lỗi siêu thực trong đó các đối tượng có thể không thuộc loại được khai báo của riêng chúng.

Việc sửa đổi trường chỉ đọc hoạt động một lần. Nhưng nếu bạn cố gắng sửa đổi nó một lần nữa, bạn sẽ gặp phải những tình huống như sau:

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

Vì vậy, đừng làm điều đó.

Đây là trên thời gian chạy Mono (công cụ trò chơi Unity).


2
FYI - Unity Engine không thể được sử dụng để trả lời hiệu quả các câu hỏi sâu về ngôn ngữ C # cụ thể như câu hỏi này bởi vì Unity thực hiện quá trình biên dịch C # của chính nó như thể .cs là script, theo một nghĩa nào đó. Tôi không nói rằng quan điểm của bạn không hợp lệ, nhưng nó chắc chắn dành riêng cho Unity Engine & C #.
Dave Jellison

0

Tôi chỉ muốn nói thêm rằng nếu bạn cần thực hiện công cụ này để kiểm tra đơn vị, thì bạn có thể sử dụng:

A) Đối tượng riêng tư lớp

B) Bạn sẽ vẫn cần một cá thể PrivateObject, nhưng bạn có thể tạo các đối tượng "Accessor" bằng Visual Studio. Cách thực hiện: Tạo lại người truy cập cá nhân

Nếu bạn đang đặt các trường riêng tư của một đối tượng trong mã của mình bên ngoài thử nghiệm đơn vị, đó sẽ là một ví dụ của "mùi mã" Tôi nghĩ rằng có lẽ lý do khác duy nhất mà bạn muốn làm điều này là nếu bạn đang giao dịch với bên thứ ba thư viện và bạn không thể thay đổi mã lớp đích. Thậm chí sau đó, bạn có thể muốn liên hệ với bên thứ 3, giải thích tình huống của bạn và xem liệu họ có tiếp tục và thay đổi mã của họ để đáp ứng nhu cầu của bạn hay không.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.