การทดสอบว่าวัตถุเป็นประเภททั่วไปใน C # หรือไม่


139

ฉันต้องการทำการทดสอบว่าวัตถุเป็นประเภททั่วไปหรือไม่ ฉันได้ลองทำสิ่งต่อไปนี้แล้วไม่ประสบความสำเร็จ:

public bool Test()
{
    List<int> list = new List<int>();
    return list.GetType() == typeof(List<>);
}

ฉันทำอะไรผิดและฉันจะทำการทดสอบนี้ได้อย่างไร?

คำตอบ:


208

หากคุณต้องการตรวจสอบว่าเป็นอินสแตนซ์ของประเภททั่วไปหรือไม่:

return list.GetType().IsGenericType;

หากคุณต้องการตรวจสอบว่าเป็นยาสามัญหรือไม่List<T>:

return list.GetType().GetGenericTypeDefinition() == typeof(List<>);

ดังที่จอนชี้ให้เห็นสิ่งนี้จะตรวจสอบความเท่าเทียมกันของประเภทที่แน่นอน การส่งคืนfalseไม่จำเป็นต้องหมายถึงlist is List<T>ผลตอบแทนfalse(กล่าวคือไม่สามารถกำหนดวัตถุให้กับList<T>ตัวแปรได้)


10
ซึ่งจะไม่ตรวจพบชนิดย่อย ดูคำตอบของฉัน มันยากกว่ามากสำหรับอินเทอร์เฟซ :(
Jon Skeet

1
การเรียกใช้ GetGenericTypeDefinition จะเกิดขึ้นหากไม่ใช่ประเภททั่วไป ตรวจสอบให้แน่ใจก่อน
Kilhoffer

86

ฉันคิดว่าคุณไม่เพียงต้องการทราบว่าประเภทเป็นแบบทั่วไปหรือไม่ แต่ถ้าวัตถุเป็นตัวอย่างของประเภททั่วไปโดยไม่ทราบประเภทอาร์กิวเมนต์

มันไม่ง่ายอย่างน่าเสียดาย ไม่เลวร้ายนักหากประเภททั่วไปเป็นคลาส (เช่นในกรณีนี้) แต่อินเทอร์เฟซจะยากกว่า นี่คือรหัสสำหรับชั้นเรียน:

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

class Test
{
    static bool IsInstanceOfGenericType(Type genericType, object instance)
    {
        Type type = instance.GetType();
        while (type != null)
        {
            if (type.IsGenericType &&
                type.GetGenericTypeDefinition() == genericType)
            {
                return true;
            }
            type = type.BaseType;
        }
        return false;
    }

    static void Main(string[] args)
    {
        // True
        Console.WriteLine(IsInstanceOfGenericType(typeof(List<>),
                                                  new List<string>()));
        // False
        Console.WriteLine(IsInstanceOfGenericType(typeof(List<>),
                                                  new string[0]));
        // True
        Console.WriteLine(IsInstanceOfGenericType(typeof(List<>),
                                                  new SubList()));
        // True
        Console.WriteLine(IsInstanceOfGenericType(typeof(List<>),
                                                  new SubList<int>()));
    }

    class SubList : List<string>
    {
    }

    class SubList<T> : List<T>
    {
    }
}

แก้ไข: ตามที่ระบุไว้ในความคิดเห็นสิ่งนี้อาจใช้ได้กับอินเทอร์เฟซ:

foreach (var i in type.GetInterfaces())
{
    if (i.IsGenericType && i.GetGenericTypeDefinition() == genericType)
    {
        return true;
    }
}

ฉันแอบสงสัยว่าอาจมีบางกรณีที่น่าอึดอัดใจในเรื่องนี้ แต่ฉันไม่พบว่ามันล้มเหลวในตอนนี้


2
เพิ่งค้นพบปัญหานี้ มันลงไปเพียงบรรทัดเดียวของการสืบทอด หากระหว่างทางคุณมีฐานที่มีทั้งคลาสพื้นฐานและอินเทอร์เฟซที่คุณกำลังมองหาสิ่งนี้จะลงไปตามเส้นทางของคลาสเท่านั้น
Groxx

1
@Groxx: จริง. ฉันเพิ่งเห็นว่าฉันพูดถึงสิ่งนั้นในคำตอบว่า: "มันไม่ได้แย่เกินไปถ้าประเภททั่วไปเป็นคลาส (ในกรณีนี้) แต่มันยากกว่าสำหรับอินเทอร์เฟซนี่คือรหัสสำหรับคลาส"
จอน Skeet

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

@RiverC: ใช่คุณขวา - มันจะค่อนข้างยากที่จะตอบด้วยเหตุผลต่างๆ หากคุณกำลังพูดถึงคลาสเท่านั้นก็ไม่เลวนัก ... คุณสามารถเดินขึ้นต้นไม้มรดกและดูว่าคุณตีList<T>ในรูปแบบใดรูปแบบหนึ่งหรืออย่างอื่น หากคุณรวมการเชื่อมต่อก็จริงๆหากิน
Jon Skeet

3
คุณไม่สามารถแทนที่ลูปในIsInstanceOfGenericTypeด้วยการเรียกIsAssignableFromแทนตัวดำเนินการความเท่าเทียมกัน ( ==) ได้หรือไม่
slawekwin

8

นี่คือสองวิธีการขยายที่ฉันชอบซึ่งครอบคลุมกรณีขอบส่วนใหญ่ของการตรวจสอบประเภททั่วไป:

ทำงานร่วมกับ:

  • หลายอินเทอร์เฟซ (ทั่วไป)
  • คลาสพื้นฐานหลายคลาส (ทั่วไป)
  • มีโอเวอร์โหลดที่จะ 'ออก' ประเภททั่วไปที่เฉพาะเจาะจงหากส่งคืนจริง (ดูการทดสอบหน่วยสำหรับตัวอย่าง):

    public static bool IsOfGenericType(this Type typeToCheck, Type genericType)
    {
        Type concreteType;
        return typeToCheck.IsOfGenericType(genericType, out concreteType); 
    }
    
    public static bool IsOfGenericType(this Type typeToCheck, Type genericType, out Type concreteGenericType)
    {
        while (true)
        {
            concreteGenericType = null;
    
            if (genericType == null)
                throw new ArgumentNullException(nameof(genericType));
    
            if (!genericType.IsGenericTypeDefinition)
                throw new ArgumentException("The definition needs to be a GenericTypeDefinition", nameof(genericType));
    
            if (typeToCheck == null || typeToCheck == typeof(object))
                return false;
    
            if (typeToCheck == genericType)
            {
                concreteGenericType = typeToCheck;
                return true;
            }
    
            if ((typeToCheck.IsGenericType ? typeToCheck.GetGenericTypeDefinition() : typeToCheck) == genericType)
            {
                concreteGenericType = typeToCheck;
                return true;
            }
    
            if (genericType.IsInterface)
                foreach (var i in typeToCheck.GetInterfaces())
                    if (i.IsOfGenericType(genericType, out concreteGenericType))
                        return true;
    
            typeToCheck = typeToCheck.BaseType;
        }
    }
    

นี่คือการทดสอบเพื่อสาธิตการทำงาน (พื้นฐาน):

 [Test]
    public void SimpleGenericInterfaces()
    {
        Assert.IsTrue(typeof(Table<string>).IsOfGenericType(typeof(IEnumerable<>)));
        Assert.IsTrue(typeof(Table<string>).IsOfGenericType(typeof(IQueryable<>)));

        Type concreteType;
        Assert.IsTrue(typeof(Table<string>).IsOfGenericType(typeof(IEnumerable<>), out concreteType));
        Assert.AreEqual(typeof(IEnumerable<string>), concreteType);

        Assert.IsTrue(typeof(Table<string>).IsOfGenericType(typeof(IQueryable<>), out concreteType));
        Assert.AreEqual(typeof(IQueryable<string>), concreteType);


    }

7

คุณสามารถใช้โค้ดที่สั้นกว่าได้โดยใช้ไดนามิกอัลทูกซึ่งอาจช้ากว่าการสะท้อนแสง:

public static class Extension
{
    public static bool IsGenericList(this object o)
    {
       return IsGeneric((dynamic)o);
    }

    public static bool IsGeneric<T>(List<T> o)
    {
       return true;
    }

    public static bool IsGeneric( object o)
    {
        return false;
    }
}



var l = new List<int>();
l.IsGenericList().Should().BeTrue();

var o = new object();
o.IsGenericList().Should().BeFalse();

0
return list.GetType().IsGenericType;

3
ถูกต้องสำหรับคำถามอื่น สำหรับคำถามนี้ไม่ถูกต้องเนื่องจากมีเพียงปัญหา (น้อยกว่า) ครึ่งหนึ่งของปัญหา
Groxx

1
คำตอบของ Stan R ตอบคำถามตามที่วางไว้ แต่สิ่งที่ OP มีความหมายจริงๆคือ "การทดสอบว่าวัตถุเป็นประเภทสามัญเฉพาะใน C # หรือไม่" ซึ่งคำตอบนี้ไม่สมบูรณ์
โยโย่

ผู้คนไม่ลงคะแนนฉันเพราะฉันตอบคำถามในบริบทของ "เป็น" ประเภททั่วไปแทนที่จะเป็น "ประเภททั่วไป" ภาษาอังกฤษเป็นภาษาที่ 2 ของฉันและความแตกต่างของภาษาดังกล่าวมักจะส่งผ่านฉันไปบ่อยครั้งเพื่อการป้องกันของฉัน OP ไม่ได้ขอให้ทดสอบกับประเภทใดประเภทหนึ่งโดยเฉพาะและในหัวข้อถามว่า "is of" ประเภททั่วไป ... ไม่แน่ใจว่าทำไมฉันจึงสมควรได้รับการโหวตลดลง คำถามที่คลุมเครือ
Stan R.

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