Làm thế nào để tôi sử dụng sự phản chiếu để gọi một phương thức riêng tư?


325

Có một nhóm các phương thức riêng tư trong lớp của tôi và tôi cần gọi một phương thức động dựa trên một giá trị đầu vào. Cả mã gọi và các phương thức đích đều trong cùng một thể hiện. Mã trông như thế này:

MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType);
dynMethod.Invoke(this, new object[] { methodParams });

Trong trường hợp này, GetMethod()sẽ không trả về các phương thức riêng tư. Có gì BindingFlagstôi cần phải cung cấp cho GetMethod()để nó có thể xác định vị trí các phương pháp tư nhân?

Câu trả lời:


497

Chỉ cần thay đổi mã của bạn để sử dụng phiên bảnGetMethod quá tải chấp nhận BindingFlags:

MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType, 
    BindingFlags.NonPublic | BindingFlags.Instance);
dynMethod.Invoke(this, new object[] { methodParams });

Đây là tài liệu liệt kê BindingFlags .


248
Tôi sẽ gặp nhiều rắc rối với việc này.
Frank Schwieterman

1
BindingFlags.NonPublickhông trả về privatephương thức .. :(
Moumit

4
@MoumitMondal là phương thức của bạn tĩnh? Bạn phải xác định BindingFlags.Instancecũng như BindingFlags.NonPubliccho các phương thức không tĩnh.
BrianS

Không có @BrianS .. phương thức này non-staticprivatelớp được kế thừa từ System.Web.UI.Page.. dù sao nó cũng khiến tôi bị lừa .. tôi không tìm thấy lý do .. :(
Moumit

3
Thêm BindingFlags.FlattenHierarchysẽ cho phép bạn có được các phương thức từ các lớp cha đến cá thể của bạn.
Dragonthoughts

67

BindingFlags.NonPublicsẽ không trả lại bất kỳ kết quả nào. Khi nó bật ra, kết hợp nó với BindingFlags.Instancethực hiện các mẹo.

MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType, 
    BindingFlags.NonPublic | BindingFlags.Instance);

Logic tương tự cũng áp dụng cho các internalchức năng
supertopi

Tôi có một vấn đề tương tự. Điều gì xảy ra nếu "đây" là một lớp con và bạn cố gắng gọi phương thức riêng của cha mẹ?
Ba Tư

Có thể sử dụng điều này để gọi các phương thức được bảo vệ của lớp base.base không?
Shiv

51

Và nếu bạn thực sự muốn gặp rắc rối, hãy thực hiện dễ dàng hơn bằng cách viết phương thức mở rộng:

static class AccessExtensions
{
    public static object call(this object o, string methodName, params object[] args)
    {
        var mi = o.GetType ().GetMethod (methodName, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance );
        if (mi != null) {
            return mi.Invoke (o, args);
        }
        return null;
    }
}

Và cách sử dụng:

    class Counter
    {
        public int count { get; private set; }
        void incr(int value) { count += value; }
    }

    [Test]
    public void making_questionable_life_choices()
    {
        Counter c = new Counter ();
        c.call ("incr", 2);             // "incr" is private !
        c.call ("incr", 3);
        Assert.AreEqual (5, c.count);
    }

14
Nguy hiểm? Đúng. Nhưng một phần mở rộng trợ giúp tuyệt vời khi được bao bọc trong không gian tên thử nghiệm đơn vị của tôi. Cám ơn vì cái này.
Robert Wahler

5
Nếu bạn quan tâm đến các trường hợp ngoại lệ thực sự được ném ra từ phương thức được gọi thì đó là một ý tưởng tốt để bọc nó vào khối bắt thử và ném lại ngoại lệ bên trong thay vào đó khi TargetInvokationException bị bắt. Tôi làm điều đó trong phần mở rộng thử nghiệm đơn vị trợ giúp của tôi.
Slobodan Savkovic

2
Phản xạ nguy hiểm? Hmmm ... C #, Java, Python ... thực sự mọi thứ đều nguy hiểm, kể cả thế giới: D Bạn chỉ cần quan tâm làm thế nào để làm điều đó một cách an toàn ...
Huyền thoại

16

Microsoft gần đây đã sửa đổi API phản chiếu kết xuất hầu hết các câu trả lời này. Sau đây nên hoạt động trên các nền tảng hiện đại (bao gồm Xamarin.Forms và UWP):

obj.GetType().GetTypeInfo().GetDeclaredMethod("MethodName").Invoke(obj, yourArgsHere);

Hoặc như một phương pháp mở rộng:

public static object InvokeMethod<T>(this T obj, string methodName, params object[] args)
{
    var type = typeof(T);
    var method = type.GetTypeInfo().GetDeclaredMethod(methodName);
    return method.Invoke(obj, args);
}

Ghi chú:

  • Nếu phương pháp mong muốn là trong một lớp cha của objcác Tchung phải được thiết lập một cách rõ ràng để loại của lớp cha.

  • Nếu phương pháp không đồng bộ, bạn có thể sử dụng await (Task) obj.InvokeMethod(…).


Nó không hoạt động cho phiên bản UWP .net ít nhất vì nó chỉ hoạt động cho các phương thức công khai : " Trả về một bộ sưu tập chứa tất cả các phương thức công khai được khai báo trên loại hiện tại khớp với tên đã chỉ định ".
Dmytro Bondarenko

1
@DmytroBondarenko Tôi đã thử nghiệm nó với các phương pháp riêng tư và nó đã hoạt động. Tôi đã thấy điều đó, tuy nhiên. Không chắc chắn tại sao nó hành xử khác với tài liệu, nhưng ít nhất nó hoạt động.
Owen James

Vâng, tôi sẽ không gọi tất cả các câu trả lời khác là lỗi thời, nếu tài liệu nói GetDeclareMethod()được dự định chỉ được sử dụng để truy xuất một phương thức công khai.
Mass Dot Net

10

Bạn có chắc chắn điều này không thể được thực hiện thông qua thừa kế? Sự phản chiếu là điều cuối cùng bạn nên xem xét khi giải quyết vấn đề, nó làm cho việc tái cấu trúc, hiểu mã của bạn và bất kỳ phân tích tự động nào trở nên khó khăn hơn.

Có vẻ như bạn chỉ cần có một lớp DrawItem1, DrawItem2, v.v. ghi đè lên dynMethod của bạn.


1
@Bill K: Do các trường hợp khác, chúng tôi quyết định không sử dụng tính kế thừa cho việc này, do đó sử dụng sự phản chiếu. Đối với hầu hết các trường hợp, chúng tôi sẽ làm theo cách đó.
Jeromy Irvine

8

Phản ánh đặc biệt là các thành viên tư nhân là sai

  • Phản xạ phá vỡ loại an toàn.Bạn có thể thử gọi một phương thức không tồn tại (nữa) hoặc với các tham số sai hoặc có quá nhiều tham số hoặc không đủ ... hoặc thậm chí theo thứ tự sai (đây là yêu thích của tôi :)). Bằng cách này, loại trả về cũng có thể thay đổi.
  • Phản xạ là chậm.

Sự phản ánh của các thành viên tư nhân phá vỡ nguyên tắc đóng gói và do đó phơi bày mã của bạn theo các điều sau:

  • Tăng độ phức tạp của mã của bạn vì nó phải xử lý hành vi bên trong của các lớp. Những gì được ẩn nên vẫn được giấu.
  • Làm cho mã của bạn dễ bị phá vỡ vì nó sẽ biên dịch nhưng sẽ không chạy nếu phương thức thay đổi tên của nó.
  • Làm cho mã riêng dễ bị phá vỡ vì nếu nó là riêng tư thì nó không có ý định được gọi theo cách đó. Có lẽ phương thức riêng mong đợi một số trạng thái bên trong trước khi được gọi.

Nếu tôi phải làm điều đó thì sao?

Có những trường hợp như vậy, khi bạn phụ thuộc vào bên thứ ba hoặc bạn cần một số api không được tiếp xúc, bạn phải thực hiện một số phản ánh. Một số người cũng sử dụng nó để kiểm tra một số lớp họ sở hữu nhưng họ không muốn thay đổi giao diện để cấp quyền truy cập cho các thành viên bên trong chỉ để kiểm tra.

Nếu bạn làm điều đó, làm điều đó đúng

  • Giảm thiểu dễ phá vỡ:

Để giảm thiểu vấn đề dễ phá vỡ, tốt nhất là phát hiện bất kỳ sự cố tiềm ẩn nào bằng cách thử nghiệm trong các thử nghiệm đơn vị sẽ chạy trong một bản dựng tích hợp liên tục hoặc như vậy. Tất nhiên, điều đó có nghĩa là bạn luôn sử dụng cùng một hội đồng (có chứa các thành viên riêng). Nếu bạn sử dụng tải trọng động và phản xạ, bạn thích chơi với lửa, nhưng bạn luôn có thể bắt Ngoại lệ mà cuộc gọi có thể tạo ra.

  • Giảm thiểu sự chậm chạp của sự phản ánh:

Trong các phiên bản gần đây của .Net Framework, CreatDelegate đánh bại bởi một yếu tố 50 mà Phương thứcInIn gọi:

// The following should be done once since this does some reflection
var method = this.GetType().GetMethod("Draw_" + itemType, 
  BindingFlags.NonPublic | BindingFlags.Instance);

// Here we create a Func that targets the instance of type which has the 
// Draw_ItemType method
var draw = (Func<TInput, Output[]>)_method.CreateDelegate(
                 typeof(Func<TInput, TOutput[]>), this);

drawcác cuộc gọi sẽ nhanh hơn khoảng 50 lần so với MethodInfo.Invoke sử dụng drawnhư một tiêu chuẩn Funcnhư thế:

var res = draw(methodParams);

Kiểm tra bài đăng này của tôi để xem điểm chuẩn trên các cách gọi khác nhau


1
Mặc dù tôi được tiêm phụ thuộc nên là một cách thử nghiệm đơn vị ưa thích, tôi đoán rằng thận trọng được sử dụng không phải là hoàn toàn xấu khi sử dụng sự phản chiếu để truy cập các đơn vị mà không thể truy cập được để thử nghiệm. Cá nhân tôi nghĩ cũng như các công cụ sửa đổi [công khai] [bảo vệ] [riêng tư] bình thường, chúng tôi cũng nên có các công cụ sửa đổi [Kiểm tra] [Thành phần] để có thể có một số thứ hiển thị trong các giai đoạn này mà không bị buộc phải làm cho mọi thứ trở nên công khai ( và do đó phải ghi lại đầy đủ các phương pháp đó)
pate

1
Cảm ơn Fab vì đã liệt kê các vấn đề về phản ánh của các thành viên tư nhân, nó đã khiến tôi phải xem lại cảm giác của mình khi sử dụng nó và đưa ra kết luận ... Sử dụng phản xạ để kiểm tra đơn vị các thành viên riêng của bạn là sai, tuy nhiên việc bỏ qua các đường dẫn mã là chưa được kiểm tra thực sự thực sự sai
pate

2
Nhưng khi nói đến thử nghiệm đơn vị mã kế thừa không thể kiểm tra đơn vị, thì đó là cách tuyệt vời để thực hiện
TS

2

Bạn có thể không chỉ có một phương thức Draw khác nhau cho từng loại mà bạn muốn Vẽ không? Sau đó gọi phương thức Draw quá tải đi qua trong đối tượng của kiểu itemType sẽ được vẽ.

Câu hỏi của bạn không làm rõ liệu itemType có thực sự đề cập đến các đối tượng thuộc các loại khác nhau hay không.


1

Tôi nghĩ rằng bạn có thể vượt qua nó BindingFlags.NonPublic, nơi GetMethodphương pháp.


1

Gọi bất kỳ phương thức nào mặc dù mức độ bảo vệ của nó trên thể hiện đối tượng. Thưởng thức!

public static object InvokeMethod(object obj, string methodName, params object[] methodParams)
{
    var methodParamTypes = methodParams?.Select(p => p.GetType()).ToArray() ?? new Type[] { };
    var bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static;
    MethodInfo method = null;
    var type = obj.GetType();
    while (method == null && type != null)
    {
        method = type.GetMethod(methodName, bindingFlags, Type.DefaultBinder, methodParamTypes, null);
        type = type.BaseType;
    }

    return method?.Invoke(obj, methodParams);
}

0

Đọc câu trả lời (bổ sung) này (đôi khi là câu trả lời) để hiểu nơi này đang diễn ra và tại sao một số người trong chủ đề này phàn nàn rằng "nó vẫn không hoạt động"

Tôi đã viết chính xác mã giống như một trong những câu trả lời ở đây . Nhưng tôi vẫn có một vấn đề. Tôi đặt điểm dừng trên

var mi = o.GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance );

Nó đã thi hành nhưng mi == null

Và nó tiếp tục hành vi như vậy cho đến khi tôi "xây dựng lại" tất cả các dự án liên quan. Tôi đã thử nghiệm đơn vị một lắp ráp trong khi phương pháp phản xạ đang ở lắp ráp thứ ba. Nó hoàn toàn khó hiểu nhưng tôi đã sử dụng Cửa sổ ngay lập tức để khám phá các phương thức và tôi thấy rằng một phương thức riêng tư mà tôi đã thử để kiểm tra đơn vị có tên cũ (tôi đã đổi tên nó). Điều này nói với tôi rằng lắp ráp cũ hoặc PDB vẫn ở ngoài đó ngay cả khi dự án thử nghiệm đơn vị được xây dựng - vì một số lý do dự án mà nó thử nghiệm không được xây dựng. "xây dựng lại" làm việc


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.