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


1070

อะไรคือวิธีที่ดีที่สุดในการเรียกใช้เมธอดทั่วไปเมื่อไม่รู้จักพารามิเตอร์ชนิดในเวลารวบรวม แต่จะได้รับแบบไดนามิก ณ รันไทม์

ลองพิจารณาโค้ดตัวอย่างต่อไปนี้ - ภายในExample()เมธอดวิธีที่กระชับที่สุดในการเรียกGenericMethod<T>()ใช้โดยTypeเก็บในmyTypeตัวแปรคืออะไร?

public class Sample
{
    public void Example(string typeName)
    {
        Type myType = FindType(typeName);

        // What goes here to call GenericMethod<T>()?
        GenericMethod<myType>(); // This doesn't work

        // What changes to call StaticMethod<T>()?
        Sample.StaticMethod<myType>(); // This also doesn't work
    }

    public void GenericMethod<T>()
    {
        // ...
    }

    public static void StaticMethod<T>()
    {
        //...
    }
}

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

12
นอกจากนี้คุณยังต้องBindingFlags.Instanceไม่เพียง แต่BindingFlags.NonPublicจะได้รับวิธีการส่วนตัว / ภายใน
Lars Kemmann

2
รุ่นทันสมัยของคำถามนี้: stackoverflow.com/q/2433436/103167
Ben Voigt

@ Peter Mortensen - fyi ฉันใช้ช่องว่างก่อนหน้า '?' เพื่อแยกส่วนภาษาอังกฤษออกจากส่วนที่ไม่ใช่ภาษาอังกฤษ (C #) IMHO ลบพื้นที่ทำให้ดูเหมือนหรือไม่ เป็นส่วนหนึ่งของรหัส หากมีรหัสไม่แน่นอนฉันเห็นด้วยกับการเอาช่องว่าง แต่ในกรณีนี้ ...
Bevan

คำตอบ:


1138

คุณต้องใช้การสะท้อนเพื่อให้ได้เมธอดเริ่มต้นจากนั้น "สร้าง" โดยการระบุอาร์กิวเมนต์ชนิดด้วยMakeGenericMethod :

MethodInfo method = typeof(Sample).GetMethod(nameof(Sample.GenericMethod));
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);

สำหรับวิธีการคงผ่านเป็นอาร์กิวเมนต์แรกnull Invokeนั่นไม่เกี่ยวกับวิธีการทั่วไป - มันเป็นแค่ภาพสะท้อนปกติ

ตามที่ระบุไว้สิ่งนี้ง่ายกว่ามากในการใช้ C # 4 dynamic- หากคุณสามารถใช้การอนุมานประเภทได้ มันไม่ได้ช่วยในกรณีที่ไม่มีการอนุมานประเภทเช่นตัวอย่างที่แน่นอนในคำถาม


92
+1; ทราบว่าGetMethod()จะพิจารณาวิธีการเช่นที่สาธารณะโดยค่าเริ่มต้นดังนั้นคุณอาจต้องและBindingFlags.Static / หรือ BindingFlags.NonPublic

20
การรวมกันที่ถูกต้องของธงคือBindingFlags.NonPublic | BindingFlags.Instance(และเลือกBindingFlags.Static)
Lars Kemmann

4
คำถามที่มีการดักจับเหยื่อของสิ่งมหัศจรรย์นี้จะทำอย่างไรกับวิธีการคงที่ - และในทางเทคนิคแล้วคำถามจะอยู่ที่นี่ พารามิเตอร์แรกของ generic.Invoke () ควรเป็นค่าว่างเมื่อเรียกใช้เมธอดแบบสแตติก พารามิเตอร์แรกจำเป็นเฉพาะเมื่อเรียกเมธอดอินสแตนซ์
Chris Moschini

2
@ChrisMoschini: เพิ่มเข้าไปในคำตอบ
Jon Skeet

2
@gzou: ฉันได้เพิ่มบางสิ่งบางอย่างที่จะตอบ - แต่ทราบว่าสำหรับการโทรวิธีการทั่วไปในคำถาม , dynamicไม่ได้ช่วยเพราะอนุมานชนิดไม่สามารถใช้ได้ (มีข้อโต้แย้งไม่มีคอมไพเลอร์สามารถใช้เพื่อกำหนดอาร์กิวเมนต์ชนิด.)
จอนสกีต

170

เป็นเพียงคำตอบเพิ่มเติมจากต้นฉบับ ขณะนี้จะใช้งานได้:

MethodInfo method = typeof(Sample).GetMethod("GenericMethod");
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);

นอกจากนี้ยังเป็นอันตรายเล็ก ๆ น้อย ๆ GenericMethodในการที่คุณจะสูญเสียการตรวจสอบรวบรวมเวลาสำหรับ หากภายหลังคุณทำการ refactoring และเปลี่ยนชื่อGenericMethodรหัสนี้จะไม่สังเกตเห็นและจะล้มเหลวในเวลาทำงาน นอกจากนี้หากมีการประมวลผลภายหลังการชุมนุม (เช่นการทำให้งงงวยหรือลบวิธีการ / ชั้นเรียนที่ไม่ได้ใช้) รหัสนี้อาจแตกเกินไป

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

Action<> GenMethod = GenericMethod<int>;  //change int by any base type 
                                          //accepted by GenericMethod
MethodInfo method = this.GetType().GetMethod(GenMethod.Method.Name);
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);

ในขณะที่ไม่สวยมากคุณมีเวลารวบรวมการอ้างอิงถึงGenericMethodที่นี่และถ้าคุณ refactor ลบหรือทำอะไรกับGenericMethodรหัสนี้จะยังคงทำงานหรืออย่างน้อยแบ่งเวลารวบรวม (ถ้าเช่นคุณลบGenericMethod)

วิธีอื่นในการทำเช่นเดียวกันคือการสร้างคลาส wrapper ใหม่และสร้างมันActivatorขึ้นมา ฉันไม่รู้ว่ามีวิธีที่ดีกว่านี้หรือไม่


5
ในกรณีที่มีการใช้การสะท้อนกลับเรียกวิธีการเป็นเรื่องปกติที่ชื่อวิธีการอื่นนั้นจะถูกค้นพบ การทราบชื่อวิธีการล่วงหน้าไม่ใช่เรื่องปกติ
Bevan

13
ฉันเห็นด้วยกับการใช้งานทั่วไปของการไตร่ตรอง แต่คำถามเดิมคือวิธีเรียก "GenericMethod <myType> ()" ถ้าอนุญาตให้ใช้ไวยากรณ์นั้นเราไม่จำเป็นต้องใช้ GetMethod () เลย แต่สำหรับคำถาม "ฉันจะเขียน" GenericMethod <myType> "ได้อย่างไรฉันคิดว่าคำตอบควรมีวิธีที่จะหลีกเลี่ยงการสูญเสียลิงค์เวลาคอมไพล์ด้วย GenericMethod ตอนนี้ถ้าคำถามนี้เป็นเรื่องปกติหรือไม่ไม่รู้ ฉันรู้ว่าฉันมีปัญหาตรงนี้เมื่อวานนี้และนั่นคือเหตุผลที่ผมเป็นเจ้าของที่ดินในคำถามนี้.
เอเดรีย Gallero

20
คุณสามารถทำแทนGenMethod.Method.GetGenericMethodDefinition() this.GetType().GetMethod(GenMethod.Method.Name)มันสะอาดกว่าเล็กน้อยและอาจปลอดภัยกว่า
Daniel Cassidy

ตัวอย่าง "myType" ของคุณหมายถึงอะไร
นักพัฒนา

37
ตอนนี้คุณสามารถใช้nameof(GenericMethod)
dmigo

140

การเรียกใช้เมธอดทั่วไปที่มีพารามิเตอร์ type ที่รู้จักกันเฉพาะ ณ รันไทม์สามารถทำให้ง่ายขึ้นอย่างมากโดยใช้dynamicชนิดแทน API การสะท้อน

ในการใช้เทคนิคนี้จะต้องรู้ชนิดจากวัตถุจริง (ไม่ใช่เพียงตัวอย่างของTypeคลาส) มิฉะนั้นคุณจะต้องสร้างวัตถุประเภทนั้นหรือใช้มาตรฐาน API สะท้อนการแก้ปัญหา คุณสามารถสร้างวัตถุโดยใช้วิธีการActivator.CreateInstance

หากคุณต้องการที่จะเรียกวิธีการทั่วไปว่าใน "ปกติ" dynamicการใช้งานจะมีประเภทของสรุปแล้วมันก็มาถึงการหล่อวัตถุชนิดที่ไม่รู้จักที่จะ นี่คือตัวอย่าง:

class Alpha { }
class Beta { }
class Service
{
    public void Process<T>(T item)
    {
        Console.WriteLine("item.GetType(): " + item.GetType()
                          + "\ttypeof(T): " + typeof(T));
    }
}

class Program
{
    static void Main(string[] args)
    {
        var a = new Alpha();
        var b = new Beta();

        var service = new Service();
        service.Process(a); // Same as "service.Process<Alpha>(a)"
        service.Process(b); // Same as "service.Process<Beta>(b)"

        var objects = new object[] { a, b };
        foreach (var o in objects)
        {
            service.Process(o); // Same as "service.Process<object>(o)"
        }
        foreach (var o in objects)
        {
            dynamic dynObj = o;
            service.Process(dynObj); // Or write "service.Process((dynamic)o)"
        }
    }
}

และนี่คือผลลัพธ์ของโปรแกรมนี้:

item.GetType(): Alpha    typeof(T): Alpha
item.GetType(): Beta     typeof(T): Beta
item.GetType(): Alpha    typeof(T): System.Object
item.GetType(): Beta     typeof(T): System.Object
item.GetType(): Alpha    typeof(T): Alpha
item.GetType(): Beta     typeof(T): Beta

Processเป็นวิธีอินสแตนซ์ทั่วไปที่เขียนชนิดที่แท้จริงของอาร์กิวเมนต์ที่ส่งผ่าน (โดยใช้GetType()วิธีการ) และชนิดของพารามิเตอร์ทั่วไป (โดยใช้typeofโอเปอเรเตอร์)

โดยการคัดเลือกอากิวเมนต์ object เพื่อdynamicพิมพ์เราเลื่อนการจัดเตรียมพารามิเตอร์ type ไว้จนกระทั่งรันไทม์ เมื่อProcessวิธีการถูกเรียกด้วยการdynamicโต้แย้งแล้วคอมไพเลอร์ไม่สนใจเกี่ยวกับประเภทของการโต้แย้งนี้ คอมไพเลอร์สร้างรหัสที่รันไทม์ตรวจสอบชนิดของอาร์กิวเมนต์ที่ส่งผ่านจริง (โดยใช้การสะท้อนกลับ) และเลือกวิธีที่ดีที่สุดในการโทร ที่นี่มีวิธีการทั่วไปเพียงวิธีเดียวเท่านั้นดังนั้นจึงเรียกใช้ด้วยพารามิเตอร์ชนิดที่เหมาะสม

ในตัวอย่างนี้ผลลัพธ์จะเหมือนกับคุณเขียนว่า:

foreach (var o in objects)
{
    MethodInfo method = typeof(Service).GetMethod("Process");
    MethodInfo generic = method.MakeGenericMethod(o.GetType());
    generic.Invoke(service, new object[] { o });
}

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

หากวิธีการทั่วไปที่คุณต้องการโทรไม่มีอาร์กิวเมนต์ประเภท parametrized (ดังนั้นพารามิเตอร์ประเภทของมันไม่สามารถอนุมานได้) จากนั้นคุณสามารถตัดคำอุทธรณ์ของวิธีการทั่วไปในวิธีผู้ช่วยเช่นในตัวอย่างต่อไปนี้:

class Program
{
    static void Main(string[] args)
    {
        object obj = new Alpha();

        Helper((dynamic)obj);
    }

    public static void Helper<T>(T obj)
    {
        GenericMethod<T>();
    }

    public static void GenericMethod<T>()
    {
        Console.WriteLine("GenericMethod<" + typeof(T) + ">");
    }
}

ความปลอดภัยประเภทที่เพิ่มขึ้น

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

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

interface IItem { }
class FooItem : IItem { }
class BarItem : IItem { }
class Alpha { }

class Program
{
    static void Main(string[] args)
    {
        var objects = new object[] { new FooItem(), new BarItem(), new Alpha() };
        for (int i = 0; i < objects.Length; i++)
        {
            ProcessItem((dynamic)objects[i], "test" + i, i);

            //ProcesItm((dynamic)objects[i], "test" + i, i);
            //compiler error: The name 'ProcesItm' does not
            //exist in the current context

            //ProcessItem((dynamic)objects[i], "test" + i);
            //error: No overload for method 'ProcessItem' takes 2 arguments
        }
    }

    static string ProcessItem<T>(T item, string text, int number)
        where T : IItem
    {
        Console.WriteLine("Generic ProcessItem<{0}>, text {1}, number:{2}",
                          typeof(T), text, number);
        return "OK";
    }
    static void ProcessItem(BarItem item, string text, int number)
    {
        Console.WriteLine("ProcessItem with Bar, " + text + ", " + number);
    }
}

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

เมื่อคุณผ่านการdynamicโต้แย้งกับวิธีการแล้วสายนี้ถูกผูกไว้เมื่อเร็ว ๆ นี้ วิธีแก้ไขการโอเวอร์โหลดเกิดขึ้นที่รันไทม์และพยายามเลือกโอเวอร์โหลดที่ดีที่สุด ดังนั้นถ้าคุณเรียกใช้ProcessItemเมธอดด้วยอ็อบเจกต์BarItemประเภทคุณจะต้องเรียกเมธอดที่ไม่ใช่แบบทั่วไปเพราะมันเป็นการจับคู่ที่ดีกว่าสำหรับประเภทนี้ อย่างไรก็ตามคุณจะได้รับข้อผิดพลาดรันไทม์เมื่อคุณผ่านการโต้แย้งของAlphaประเภทเพราะไม่มีวิธีที่สามารถจัดการวัตถุนี้ (วิธีการทั่วไปมีข้อ จำกัดwhere T : IItemและAlphaชั้นไม่ได้ใช้อินเตอร์เฟซนี้) แต่นั่นคือประเด็นทั้งหมด คอมไพเลอร์ไม่มีข้อมูลว่าการโทรนี้ถูกต้อง คุณในฐานะโปรแกรมเมอร์รู้เรื่องนี้และคุณควรตรวจสอบให้แน่ใจว่าโค้ดนี้ทำงานโดยไม่มีข้อผิดพลาด

ประเภทผลตอบแทน gotcha

เมื่อคุณเรียกใช้เมธอดที่ไม่เป็นโมฆะโดยใช้พารามิเตอร์ชนิดไดนามิกประเภทการส่งคืนอาจเป็นdynamicเช่นนั้น ดังนั้นหากคุณต้องการเปลี่ยนตัวอย่างก่อนหน้าเป็นรหัสนี้:

var result = ProcessItem((dynamic)testObjects[i], "test" + i, i);

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

string result = ProcessItem((dynamic)testObjects[i], "test" + i, i);

คุณจะได้รับข้อผิดพลาดรันไทม์หากประเภทไม่ตรงกัน

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


Mariusz สับสนโดย "อย่างไรก็ตามคุณจะได้รับข้อผิดพลาดรันไทม์เมื่อคุณส่งอาร์กิวเมนต์ประเภท Alpha เนื่องจากไม่มีวิธีที่สามารถจัดการกับวัตถุนี้ได้" ถ้าฉันเรียก var a = new Alpha () ProcessItem (a, "test" + i , i) ทำไมเมธอดทั่วไป ProcessItem ไม่จัดการสิ่งนี้อย่างมีประสิทธิภาพโดยแสดงผลลัพธ์ "รายการกระบวนการทั่วไป"
Alex Edelstein

@AlexEdelstein ฉันแก้ไขคำตอบของฉันเพื่อชี้แจงเล็กน้อย เป็นเพราะProcessItemวิธีการทั่วไปมีข้อ จำกัด ทั่วไปและยอมรับเฉพาะวัตถุที่ใช้IItemอินเทอร์เฟซ เมื่อคุณจะโทรProcessItem(new Aplha(), "test" , 1);หรือProcessItem((object)(new Aplha()), "test" , 1);คุณจะได้รับข้อผิดพลาดของคอมไพเลอร์ แต่เมื่อส่งให้dynamicคุณเลื่อนการตรวจสอบเพื่อรันไทม์
Mariusz Pawelski

คำตอบและคำอธิบายที่ยอดเยี่ยมทำงานได้อย่างสมบูรณ์แบบสำหรับฉัน ดีกว่าคำตอบที่ยอมรับสั้นกว่าการเขียนนักแสดงมากขึ้นและปลอดภัยยิ่งขึ้น
ygoe

17

ด้วย C # 4.0 การสะท้อนนั้นไม่จำเป็นเนื่องจาก DLR สามารถเรียกใช้โดยใช้ประเภทรันไทม์ เนื่องจากการใช้ไลบรารี DLR เป็นชนิดของความเจ็บปวดแบบไดนามิก (แทนรหัสสร้าง C # คอมไพเลอร์สำหรับคุณ), กรอบงานโอเพ่นซอร์สDynamitey (.net มาตรฐาน 1.5) ช่วยให้คุณเข้าถึงการเรียกใช้แคชในเวลาเดียวกันกับการเรียกคอมไพเลอร์ สำหรับคุณ.

var name = InvokeMemberName.Create;
Dynamic.InvokeMemberAction(this, name("GenericMethod", new[]{myType}));


var staticContext = InvokeContext.CreateStatic;
Dynamic.InvokeMemberAction(staticContext(typeof(Sample)), name("StaticMethod", new[]{myType}));

13

การเพิ่มคำตอบของ Adrian Gallero :

การเรียกวิธีการทั่วไปจากข้อมูลประเภทเกี่ยวข้องกับสามขั้นตอน

TLDR: การเรียกเมธอดทั่วไปที่รู้จักด้วยอ็อบเจ็กต์ชนิดสามารถทำได้โดย:

((Action)GenericMethod<object>)
    .Method
    .GetGenericMethodDefinition()
    .MakeGenericMethod(typeof(string))
    .Invoke(this, null);

โดยที่GenericMethod<object>เป็นชื่อวิธีการโทรและประเภทใด ๆ ที่เป็นไปตามข้อ จำกัด ทั่วไป

(การกระทำ) ตรงกับลายเซ็นของวิธีการที่จะเรียกว่า ( Func<string,string,int>หรือAction<bool>)

ขั้นตอนที่ 1 ได้รับ MethodInfo สำหรับคำจำกัดความวิธีการทั่วไป

วิธีที่ 1: ใช้ GetMethod () หรือ GetMethods () ด้วยประเภทที่เหมาะสมหรือการตั้งค่าสถานะการผูก

MethodInfo method = typeof(Sample).GetMethod("GenericMethod");

วิธีที่ 2: สร้างผู้รับมอบสิทธิ์รับวัตถุ MethodInfo แล้วเรียกใช้ GetGenericMethodDefinition

จากภายในชั้นเรียนที่มีวิธีการ:

MethodInfo method = ((Action)GenericMethod<object>)
    .Method
    .GetGenericMethodDefinition();

MethodInfo method = ((Action)StaticMethod<object>)
    .Method
    .GetGenericMethodDefinition();

จากภายนอกคลาสที่มีเมธอด:

MethodInfo method = ((Action)(new Sample())
    .GenericMethod<object>)
    .Method
    .GetGenericMethodDefinition();

MethodInfo method = ((Action)Sample.StaticMethod<object>)
    .Method
    .GetGenericMethodDefinition();

ใน C # ชื่อของวิธีการคือ "ToString" หรือ "GenericMethod" หมายถึงกลุ่มของวิธีการที่อาจมีวิธีการหนึ่งวิธีหรือมากกว่า จนกว่าคุณจะระบุประเภทของพารามิเตอร์วิธีการจะไม่ทราบว่าวิธีการที่คุณอ้างถึง

((Action)GenericMethod<object>)อ้างถึงผู้รับมอบสิทธิ์สำหรับวิธีการเฉพาะ ((Func<string, int>)GenericMethod<object>) หมายถึงโอเวอร์โหลดที่แตกต่างกันของ GenericMethod

วิธีที่ 3: สร้างนิพจน์แลมบ์ดาที่มีนิพจน์การเรียกเมธอดรับวัตถุ MethodInfo จากนั้น GetGenericMethodDefinition

MethodInfo method = ((MethodCallExpression)((Expression<Action<Sample>>)(
    (Sample v) => v.GenericMethod<object>()
    )).Body).Method.GetGenericMethodDefinition();

สิ่งนี้พังทลายลงมา

สร้างการแสดงออกแลมบ์ดาซึ่งร่างกายใช้การเรียกไปยังวิธีที่คุณต้องการ

Expression<Action<Sample>> expr = (Sample v) => v.GenericMethod<object>();

แยกร่างและส่งไปที่ MethodCall Expression

MethodCallExpression methodCallExpr = (MethodCallExpression)expr.Body;

รับการกำหนดวิธีการทั่วไปจากวิธีการ

MethodInfo methodA = methodCallExpr.Method.GetGenericMethodDefinition();

ขั้นตอนที่ 2 เรียกใช้ MakeGenericMethod เพื่อสร้างวิธีการทั่วไปที่มีประเภทที่เหมาะสม

MethodInfo generic = method.MakeGenericMethod(myType);

ขั้นตอนที่ 3 คือการเรียกใช้เมธอดด้วยอาร์กิวเมนต์ที่เหมาะสม

generic.Invoke(this, null);

8

ไม่มีใครได้ให้โซลูชัน " ภาพสะท้อนแบบคลาสสิค " ดังนั้นนี่คือตัวอย่างโค้ดแบบสมบูรณ์:

using System;
using System.Collections;
using System.Collections.Generic;

namespace DictionaryRuntime
{
    public class DynamicDictionaryFactory
    {
        /// <summary>
        /// Factory to create dynamically a generic Dictionary.
        /// </summary>
        public IDictionary CreateDynamicGenericInstance(Type keyType, Type valueType)
        {
            //Creating the Dictionary.
            Type typeDict = typeof(Dictionary<,>);

            //Creating KeyValue Type for Dictionary.
            Type[] typeArgs = { keyType, valueType };

            //Passing the Type and create Dictionary Type.
            Type genericType = typeDict.MakeGenericType(typeArgs);

            //Creating Instance for Dictionary<K,T>.
            IDictionary d = Activator.CreateInstance(genericType) as IDictionary;

            return d;

        }
    }
}

ดังกล่าวข้างต้นDynamicDictionaryFactoryชั้นจะมีวิธีการ

CreateDynamicGenericInstance(Type keyType, Type valueType)

และจะสร้างและส่งกลับอินสแตนซ์ IDictionary ประเภทของที่มีคีย์และค่าจะตรงตามที่ระบุไว้ในการโทรและkeyTypevalueType

นี่คือตัวอย่างที่สมบูรณ์วิธีเรียกใช้เมธอดนี้เพื่อยกตัวอย่างและใช้Dictionary<String, int>:

using System;
using System.Collections.Generic;

namespace DynamicDictionary
{
    class Test
    {
        static void Main(string[] args)
        {
            var factory = new DictionaryRuntime.DynamicDictionaryFactory();
            var dict = factory.CreateDynamicGenericInstance(typeof(String), typeof(int));

            var typedDict = dict as Dictionary<String, int>;

            if (typedDict != null)
            {
                Console.WriteLine("Dictionary<String, int>");

                typedDict.Add("One", 1);
                typedDict.Add("Two", 2);
                typedDict.Add("Three", 3);

                foreach(var kvp in typedDict)
                {
                    Console.WriteLine("\"" + kvp.Key + "\": " + kvp.Value);
                }
            }
            else
                Console.WriteLine("null");
        }
    }
}

เมื่อแอปพลิเคชันคอนโซลด้านบนถูกเรียกใช้งานเราจะได้รับผลลัพธ์ที่ถูกต้องและคาดหวัง:

Dictionary<String, int>
"One": 1
"Two": 2
"Three": 3

2

นี่คือ 2 เซ็นต์ของฉันตามคำตอบของ Graxแต่มีสองพารามิเตอร์ที่จำเป็นสำหรับวิธีการทั่วไป

สมมติว่าเมธอดของคุณถูกกำหนดดังนี้ในคลาส Helpers:

public class Helpers
{
    public static U ConvertCsvDataToCollection<U, T>(string csvData)
    where U : ObservableCollection<T>
    {
      //transform code here
    }
}

ในกรณีของฉันพิมพ์ U เป็นคอลเลกชันที่สังเกตได้ซึ่งจัดเก็บวัตถุประเภท T เสมอ

เนื่องจากฉันมีประเภทที่กำหนดไว้ล่วงหน้าฉันจะสร้างวัตถุ "จำลอง" ที่แสดงคอลเลกชันที่สังเกตได้ (U) และวัตถุที่เก็บอยู่ในนั้น (T) และจะใช้ด้านล่างเพื่อรับชนิดของพวกเขาเมื่อเรียกใช้ Make

object myCollection = Activator.CreateInstance(collectionType);
object myoObject = Activator.CreateInstance(objectType);

จากนั้นเรียกใช้ GetMethod เพื่อค้นหาฟังก์ชันทั่วไป:

MethodInfo method = typeof(Helpers).
GetMethod("ConvertCsvDataToCollection");

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

คุณต้องผ่านอาร์เรย์ Type [] ไปยังฟังก์ชัน MakeGenericMethod ที่มีประเภทวัตถุ "จำลอง" ที่สร้างขึ้นด้านบน:

MethodInfo generic = method.MakeGenericMethod(
new Type[] {
   myCollection.GetType(),
   myObject.GetType()
});

เมื่อเสร็จแล้วคุณจะต้องเรียกใช้วิธีการเรียกใช้ตามที่กล่าวไว้ข้างต้น

generic.Invoke(null, new object[] { csvData });

และคุณทำเสร็จแล้ว ใช้เสน่ห์ได้!

UPDATE:

เป็น @Bevan ที่เน้นฉันไม่จำเป็นต้องสร้างอาร์เรย์เมื่อเรียกใช้ฟังก์ชัน MakeGenericMethod เหมือนที่ใช้ใน params และฉันไม่จำเป็นต้องสร้างวัตถุเพื่อรับชนิดเนื่องจากฉันสามารถส่งประเภทไปยังฟังก์ชันนี้ได้โดยตรง ในกรณีของฉันเนื่องจากฉันมีประเภทที่กำหนดไว้ล่วงหน้าในคลาสอื่นฉันก็เปลี่ยนรหัสของฉันเป็น:

object myCollection = null;

MethodInfo method = typeof(Helpers).
GetMethod("ConvertCsvDataToCollection");

MethodInfo generic = method.MakeGenericMethod(
   myClassInfo.CollectionType,
   myClassInfo.ObjectType
);

myCollection = generic.Invoke(null, new object[] { csvData });

myClassInfo มีคุณสมบัติของประเภทTypeที่ฉันตั้งค่าในเวลาทำงาน 2 ตามค่า enum ส่งผ่านไปยังตัวสร้างและจะให้ฉันกับประเภทที่เกี่ยวข้องซึ่งฉันใช้ใน MakeGenericMethod

ขอขอบคุณอีกครั้งสำหรับการเน้น @Bevan นี้


อาร์กิวเมนต์ที่MakeGenericMethod()มีคำหลักparamsดังนั้นคุณไม่จำเป็นต้องสร้างอาร์เรย์ คุณไม่จำเป็นต้องสร้างอินสแตนซ์เพื่อให้ได้ชนิด - methodInfo.MakeGenericMethod(typeof(TCollection), typeof(TObject))จะเพียงพอ
Bevan

0

แรงบันดาลใจจากคำตอบของ Enigmativityสมมติว่าคุณมีสองคลาส (หรือมากกว่า) เช่น

public class Bar { }
public class Square { }

และคุณต้องการเรียกใช้เมธอดFoo<T>ด้วยBarและSquareซึ่งถูกประกาศว่าเป็น

public class myClass
{
    public void Foo<T>(T item)
    {
        Console.WriteLine(typeof(T).Name);
    }
}

จากนั้นคุณสามารถใช้วิธีการส่วนขยายเช่น:

public static class Extension
{
    public static void InvokeFoo<T>(this T t)
    {
        var fooMethod = typeof(myClass).GetMethod("Foo");
        var tType = typeof(T);
        var fooTMethod = fooMethod.MakeGenericMethod(new[] { tType });
        fooTMethod.Invoke(new myClass(), new object[] { t });
    }
}

ด้วยสิ่งนี้คุณสามารถเรียกFooเช่น:

var objSquare = new Square();
objSquare.InvokeFoo();

var objBar = new Bar();
objBar.InvokeFoo();

ซึ่งใช้ได้กับทุกชั้นเรียน ในกรณีนี้มันจะออก:

สแควร์
บาร์

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