ผ่าน System.Type ที่สร้างอินสแตนซ์เป็นพารามิเตอร์ประเภทสำหรับคลาสทั่วไป


183

ชื่อนั้นค่อนข้างคลุมเครือ สิ่งที่ฉันอยากรู้คือถ้าเป็นไปได้:

string typeName = <read type name from somwhere>;
Type myType = Type.GetType(typeName);

MyGenericClass<myType> myGenericClass = new MyGenericClass<myType>();

เห็นได้ชัดว่า MyGenericClass อธิบายไว้เป็น:

public class MyGenericClass<T>

ตอนนี้คอมไพเลอร์บ่นว่าไม่พบ 'ประเภทหรือเนมสเปซ' myType '"ต้องมีวิธีในการทำเช่นนี้


Generics! = เทมเพลต ตัวแปรชนิดทั่วไปทั้งหมดได้รับการแก้ไขในเวลารวบรวมและไม่ใช่เวลารันไทม์ นี่เป็นหนึ่งในสถานการณ์ที่ประเภท 'ไดนามิก' ของ 4.0 อาจมีประโยชน์

1
@Will - ในทางใด เมื่อใช้กับ generics ภายใต้ CTP ปัจจุบันคุณจำเป็นต้องเรียกรุ่น <object> (นอกเสียจากว่าฉันขาดเล่ห์อุบาย ... )
Marc Gravell

@MarcGravell คุณสามารถใช้foo.Method((dynamic)myGenericClass)สำหรับการเชื่อมเมธอดเวลาทำงานได้อย่างมีประสิทธิภาพรูปแบบตัวระบุตำแหน่งบริการสำหรับเมธอด overloads ของชนิด
Chris Marisic

@ChrisMarisic ใช่สำหรับบางคนทั่วไปpublic void Method<T>(T obj)- เคล็ดลับที่ฉันได้ใช้มากกว่าสองสามครั้งในช่วง 6 ปีที่ผ่านมาตั้งแต่ความคิดเห็นนั้น p
Marc Gravell

@MarcGravell มีวิธีการแก้ไขเพื่อให้วิธี instantiates มันได้หรือไม่
barlop

คำตอบ:


220

คุณไม่สามารถทำสิ่งนี้ได้โดยปราศจากการไตร่ตรอง อย่างไรก็ตามคุณสามารถทำได้ด้วยการสะท้อนกลับ นี่คือตัวอย่างที่สมบูรณ์:

using System;
using System.Reflection;

public class Generic<T>
{
    public Generic()
    {
        Console.WriteLine("T={0}", typeof(T));
    }
}

class Test
{
    static void Main()
    {
        string typeName = "System.String";
        Type typeArgument = Type.GetType(typeName);

        Type genericClass = typeof(Generic<>);
        // MakeGenericType is badly named
        Type constructedClass = genericClass.MakeGenericType(typeArgument);

        object created = Activator.CreateInstance(constructedClass);
    }
}

หมายเหตุ: หากคลาสทั่วไปของคุณยอมรับหลายประเภทคุณจะต้องใส่เครื่องหมายจุลภาคเมื่อคุณไม่ใส่ชื่อประเภทเช่น

Type genericClass = typeof(IReadOnlyDictionary<,>);
Type constructedClass = genericClass.MakeGenericType(typeArgument1, typeArgument2);

1
ตกลงนี้เป็นสิ่งที่ดี แต่วิธีการหนึ่งที่เกี่ยวกับวิธีการโทรในการสร้าง? สะท้อนเพิ่มเติมหรือไม่
Robert C. Barth

7
ถ้าคุณสามารถหลีกเลี่ยงการทำให้ประเภททั่วไปของคุณใช้อินเทอร์เฟซที่ไม่ใช่แบบทั่วไปคุณสามารถส่งไปยังส่วนต่อประสานนั้นได้ อีกวิธีหนึ่งคุณสามารถเขียนวิธีการทั่วไปของคุณเองซึ่งทำงานทุกอย่างที่คุณต้องการทำกับสามัญและเรียกสิ่งนั้นด้วยการไตร่ตรอง
Jon Skeet

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

6
@ RobertC.Barth นอกจากนี้คุณยังสามารถสร้างวัตถุ "สร้าง" ในตัวอย่างประเภท "ไดนามิก" แทน "วัตถุ" ด้วยวิธีนี้คุณสามารถเรียกใช้เมธอดได้และการประเมินผลจะถูกเลื่อนออกไปจนกระทั่งรันไทม์
McGarnagle

4
@balanza: คุณใช้ MakeGenericMethod
Jon Skeet

14

น่าเสียดายที่ไม่มี อาร์กิวเมนต์ทั่วไปต้องสามารถแก้ไขได้ที่ Compile time เนื่องจาก 1) ชนิดที่ถูกต้องหรือ 2) พารามิเตอร์ทั่วไปอื่น ไม่มีวิธีในการสร้างอินสแตนซ์ทั่วไปตามค่ารันไทม์โดยไม่ต้องใช้ค้อนขนาดใหญ่ในการใช้การสะท้อน


2

เพิ่มเติมบางวิธีการเรียกใช้ด้วยรหัสกรรไกร สมมติว่าคุณมีคลาสที่คล้ายกับ

public class Encoder() {
public void Markdown(IEnumerable<FooContent> contents) { do magic }
public void Markdown(IEnumerable<BarContent> contents) { do magic2 }
}

สมมติว่าตอนรันไทม์คุณมีFooContent

หากคุณสามารถผูกเวลารวบรวมคุณจะต้องการ

var fooContents = new List<FooContent>(fooContent)
new Encoder().Markdown(fooContents)

อย่างไรก็ตามคุณไม่สามารถทำได้ในขณะใช้งานจริง ในการทำเช่นนี้ที่ runtime คุณต้องทำตามบรรทัดของ

var listType = typeof(List<>).MakeGenericType(myType);
var dynamicList = Activator.CreateInstance(listType);
((IList)dynamicList).Add(fooContent);

เพื่อเรียกใช้แบบไดนามิก Markdown(IEnumerable<FooContent> contents)

new Encoder().Markdown( (dynamic) dynamicList)

หมายเหตุการใช้งานdynamicในการเรียกใช้เมธอด ที่รันไทม์dynamicListจะเป็นList<FooContent>(นอกจากนี้ยังเป็นIEnumerable<FooContent>) เนื่องจากแม้กระทั่งการใช้งานของแบบไดนามิกยังคงหยั่งรากไปเป็นภาษาที่พิมพ์อย่างยิ่ง binder เวลาทำงานจะเลือกMarkdownวิธีการที่เหมาะสม หากไม่มีประเภทที่ตรงกันทั้งหมดก็จะค้นหาวิธีการพารามิเตอร์วัตถุและถ้าไม่ตรงกับข้อยกเว้น Binder รันไทม์จะถูกยกขึ้นแจ้งเตือนว่าไม่มีวิธีการที่ตรงกับ

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


2

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

namespace GenericTest
{
    public class Item
    {
    }
}

namespace GenericTest
{
    public class GenericClass<T>
    {
    }
}

ในที่สุดนี่คือวิธีที่คุณเรียกว่า กำหนดประเภทที่มี backtick

var t = Type.GetType("GenericTest.GenericClass`1[[GenericTest.Item, GenericTest]], GenericTest");
var a = Activator.CreateInstance(t);

0

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

public class Type1 { }

public class Type2 { }

public class Generic<T> { }

public class Program
{
    public static void Main()
    {
        var typeName = nameof(Type1);

        switch (typeName)
        {
            case nameof(Type1):
                var type1 = new Generic<Type1>();
                // do something
                break;
            case nameof(Type2):
                var type2 = new Generic<Type2>();
                // do something
                break;
        }
    }
}

สิ่งนี้จะเร็วขึ้นอย่างน่าเกลียดเมื่อคุณเริ่มจัดการคลาส 100 ครั้ง
michael g

0

ในตัวอย่างนี้ฉันต้องการแสดงวิธีการสร้างและใช้รายการที่สร้างขึ้นแบบไดนามิก ตัวอย่างเช่นฉันกำลังเพิ่มลงในรายการแบบไดนามิกที่นี่

void AddValue<T>(object targetList, T valueToAdd)
{
    var addMethod = targetList.GetType().GetMethod("Add");
    addMethod.Invoke(targetList, new[] { valueToAdd } as object[]);
}

var listType = typeof(List<>).MakeGenericType(new[] { dynamicType }); // dynamicType is the type you want
var list = Activator.CreateInstance(listType);

AddValue(list, 5);

ในทำนองเดียวกันคุณสามารถเรียกใช้วิธีการอื่นในรายการ

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