ฉันจะใช้การสะท้อนกลับเพื่อเรียกใช้วิธีการส่วนตัวได้อย่างไร


326

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

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

ในกรณีนี้GetMethod()จะไม่ส่งคืนวิธีการส่วนตัว สิ่งที่BindingFlagsผมจะต้องจ่ายให้กับGetMethod()เพื่อที่จะสามารถหาวิธีเอกชน?

คำตอบ:


498

เพียงเปลี่ยนรหัสของคุณเพื่อใช้เวอร์ชันที่GetMethodโอเวอร์โหลดซึ่งยอมรับ BindingFlags:

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

นี่คือเอกสารประกอบการแจงนับ BindingFlags


248
ฉันจะทำให้ตัวเองมีปัญหามากกับเรื่องนี้
Frank Schwieterman

1
BindingFlags.NonPublicไม่กลับprivateวิธี .. :(
Moumit

4
@MoumitMondal วิธีการของคุณคงที่? คุณต้องระบุBindingFlags.Instanceเช่นเดียวกับวิธีBindingFlags.NonPublicการไม่คงที่
BrianS

ไม่ @BrianS .. วิธีการคือnon-staticและprivateและชั้นเรียนที่ได้รับมาจากSystem.Web.UI.Page.. มันทำให้ฉันโง่อยู่ดี .. ฉันไม่พบเหตุผล .. :(
Moumit

3
การเพิ่มBindingFlags.FlattenHierarchyจะช่วยให้คุณรับเมธอดจากคลาสพาเรนต์ไปยังอินสแตนซ์ของคุณ
Dragon Thoughts

67

BindingFlags.NonPublicจะไม่ส่งคืนผลลัพธ์ใด ๆ ด้วยตัวเอง เมื่อมันปรากฏออกมาการรวมมันเข้ากับBindingFlags.Instanceเล่ห์เหลี่ยม

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

ตรรกะเดียวกันนี้ใช้กับinternalฟังก์ชันต่างๆเช่นกัน
supertopi

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

เป็นไปได้หรือไม่ที่จะใช้สิ่งนี้เพื่อเรียกใช้เมธอดที่ป้องกันคลาส base.base?
ชีฟ

51

และถ้าคุณต้องการที่จะประสบปัญหาจริงๆให้ง่ายต่อการดำเนินการโดยการเขียนวิธีการขยาย:

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

และการใช้งาน:

    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
อันตรายหรือไม่? ใช่. แต่นามสกุลผู้ช่วยที่ดีเมื่อห่อใน namespace ทดสอบหน่วยของฉัน ขอบคุณสำหรับสิ่งนี้.
Robert Wahler

5
หากคุณสนใจข้อยกเว้นจริง ๆ ที่ถูกโยนออกมาจากวิธีการที่เรียกว่าเป็นความคิดที่ดีที่จะห่อมันไว้ในลอง catch catch block และ re-Throw exception inner แทนเมื่อจับ TargetInvokationException ฉันทำอย่างนั้นในส่วนขยายตัวช่วยทดสอบหน่วยของฉัน
Slobodan Savkovic

2
ภาพสะท้อนอันตรายหรือไม่? อืม ... C #, Java, Python ... จริง ๆ แล้วทุกอย่างเป็นอันตรายแม้แต่โลก: D คุณแค่ต้องดูแลเกี่ยวกับวิธีที่จะทำอย่างปลอดภัย ...
ตำนาน

16

Microsoft เพิ่งแก้ไขการสะท้อน API ที่แสดงผลคำตอบเหล่านี้ส่วนใหญ่ล้าสมัย ต่อไปนี้ควรทำงานบนแพลตฟอร์มที่ทันสมัย ​​(รวมถึง Xamarin.Forms และ UWP):

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

หรือเป็นวิธีการขยาย:

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

บันทึก:

  • หากวิธีการที่ต้องการอยู่ใน superclass ของทั่วไปจะต้องมีการกำหนดอย่างชัดเจนกับชนิดของ superclass ที่objT

  • await (Task) obj.InvokeMethod(…)หากวิธีการที่ไม่ตรงกันคุณสามารถใช้


ไม่ทำงานสำหรับรุ่น. UWP สุทธิอย่างน้อยเนื่องจากจะใช้ได้กับวิธีการสาธารณะเท่านั้น: " ส่งคืนคอลเลกชันที่ประกอบด้วยวิธีสาธารณะทั้งหมดที่ประกาศในประเภทปัจจุบันที่ตรงกับชื่อที่ระบุ "
Dmytro Bondarenko

1
@DmytroBondarenko ฉันทดสอบกับวิธีส่วนตัวและใช้งานได้ อย่างไรก็ตามฉันเห็นอย่างนั้น ไม่แน่ใจว่าทำไมมันทำงานแตกต่างจากเอกสาร แต่อย่างน้อยก็ใช้งานได้
โอเว่นเจมส์

ใช่ฉันจะไม่เรียกคำตอบอื่น ๆ ทั้งหมดที่ล้าสมัยถ้าเอกสารกล่าวว่าGetDeclareMethod()มีวัตถุประสงค์เพื่อใช้ในการเรียกคืนเฉพาะวิธีการสาธารณะ
Mass Dot Net

10

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

ดูเหมือนว่าคุณควรมีคลาส DrawItem1, DrawItem2 และอื่น ๆ ที่แทนที่ dynMethod ของคุณ


1
@ บิล K: จากสถานการณ์อื่นเราตัดสินใจที่จะไม่ใช้มรดกสำหรับสิ่งนี้ดังนั้นการใช้ภาพสะท้อน สำหรับกรณีส่วนใหญ่เราจะทำเช่นนั้น
Jeromy Irvine

8

การไตร่ตรองโดยเฉพาะกับสมาชิกส่วนตัวนั้นผิด

  • การสะท้อนการแบ่งความปลอดภัยประเภท คุณสามารถลองเรียกใช้วิธีการที่ไม่มีอยู่ (อีกต่อไป) หรือด้วยพารามิเตอร์ที่ไม่ถูกต้องหรือมีพารามิเตอร์มากเกินไปหรือไม่เพียงพอ ... หรือแม้กระทั่งในลำดับที่ไม่ถูกต้อง โดยวิธีการคืนชนิดสามารถเปลี่ยนแปลงได้เช่นกัน
  • การสะท้อนกลับช้า

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

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

ถ้าฉันต้องทำยังไงล่ะ

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

ถ้าคุณทำมันถูกต้อง

  • ลดความง่ายที่จะทำลาย:

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

  • บรรเทาความเชื่องช้าของการสะท้อน:

ใน. Net Framework เวอร์ชันล่าสุด CreateDelegate จะได้รับผลกระทบจากปัจจัย 50 ที่ MethodInfo เรียกใช้:

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

drawการโทรจะเร็วกว่าประมาณ 50 เท่าเมื่อMethodInfo.Invoke ใช้งานdrawตามมาตรฐานFuncเช่นนั้น:

var res = draw(methodParams);

ตรวจสอบโพสต์ของฉันนี้เพื่อดูเกณฑ์มาตรฐานในวิธีการขอร้องที่แตกต่างกัน


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

1
ขอบคุณ Fab ที่แสดงปัญหาการสะท้อนสมาชิกส่วนตัวทำให้ฉันทบทวนว่าฉันรู้สึกอย่างไรเกี่ยวกับการใช้งานและได้ข้อสรุป ... การใช้การไตร่ตรองเพื่อทดสอบหน่วยสมาชิกส่วนตัวของคุณไม่ถูกต้อง ผิดจริงๆ
andrew pate

2
แต่เมื่อมันมาถึงการทดสอบหน่วยของรหัสดั้งเดิมที่ไม่สามารถทดสอบได้มันเป็นวิธีที่ยอดเยี่ยมที่จะทำมัน
TS

2

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

คำถามของคุณไม่ได้ทำให้ชัดเจนว่า itemType หมายถึงวัตถุประเภทที่แตกต่างกันอย่างแท้จริง



1

เรียกใช้วิธีการใด ๆ แม้จะมีระดับการป้องกันในอินสแตนซ์ของวัตถุ สนุก!

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

อ่านคำตอบ (เสริม) นี้ (ซึ่งบางครั้งก็เป็นคำตอบ) เพื่อทำความเข้าใจว่าสิ่งนี้เกิดขึ้นที่ใดและทำไมบางคนในกระทู้นี้จึงบ่นว่า"มันยังไม่ทำงาน"

ฉันเขียนรหัสเดียวกันกับคำตอบข้อใดข้อหนึ่งตรงนี้ แต่ฉันยังคงมีปัญหา ฉันวางจุดพักบน

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

มันดำเนินการ แต่ mi == null

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


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