ตรวจสอบว่าคลาสนั้นมาจากคลาสทั่วไปหรือไม่


309

ฉันมีชั้นเรียนทั่วไปในโครงการของฉันกับชั้นเรียนที่ได้รับ

public class GenericClass<T> : GenericInterface<T>
{
}

public class Test : GenericClass<SomeType>
{
}

มีวิธีใดบ้างที่จะทราบว่าTypeวัตถุนั้นได้มาจากGenericClassอะไร?

t.IsSubclassOf(typeof(GenericClass<>))

ไม่ทำงาน, ไม่เป็นผล.

คำตอบ:


430

ลองรหัสนี้

static bool IsSubclassOfRawGeneric(Type generic, Type toCheck) {
    while (toCheck != null && toCheck != typeof(object)) {
        var cur = toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck;
        if (generic == cur) {
            return true;
        }
        toCheck = toCheck.BaseType;
    }
    return false;
}

4
นี่เป็นโค้ดหวาน ๆ ที่ฉันต้องบอก การดำเนินการวนรอบในขณะที่หลีกเลี่ยงประสิทธิภาพการเรียกซ้ำที่ไม่จำเป็นยัง มันเป็นคำตอบที่สง่างามและสวยงามสำหรับคำถามทั่วไป
EnocNRoll - AnandaGopal Pardue

2
ฉันได้เพิ่มวิธีนี้ให้กับคลาสแบบคงที่ ReflectionUtils ของฉันในกรอบงานของฉันและฉันได้ดัดแปลงเป็นวิธีการขยายสำหรับวัตถุโดยกำหนด toCheck ภายในวิธีเป็น Type toCheck = obj.GetType (); กำหนด "วัตถุนี้ obj" เป็นพารามิเตอร์แรก
EnocNRoll - AnandaGopal Pardue

11
ขณะที่ลูปจะไม่แตกถ้าประเภท toCheck ไม่ใช่คลาส (เช่นอินเตอร์เฟส) สิ่งนี้จะทำให้เกิด NullReferenceException
JD Courtoy

2
สิ่งนี้จะกลับมาเป็นจริงหาก toCheck เป็นประเภททั่วไปที่คุณต้องการ
oillio

14
ใช้งานได้เฉพาะกับการสืบทอดประเภทที่เป็นรูปธรรม ... กรณีทดสอบ: bool คาดหวัง = จริง; บูลจริง = Program.IsSubclassOfRawGeneric (typeof (IEnumerable <>), typeof (รายการ <string>)); Assert.AreEqual (คาดว่าจะเกิดขึ้นจริง); // ล้มเหลว
Bobby

90

(โพสต์ใหม่เนื่องจากมีการเขียนซ้ำมาก)

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

เคล็ดลับ:

หากคุณแน่ใจว่าการใช้งาน GenericClass ของคุณสืบทอดมาจากคลาสพื้นฐานที่ไม่ใช่แบบทั่วไปเช่น GenericClassBase คุณสามารถถามคำถามเดียวกันโดยไม่มีปัญหาใด ๆ เช่นนี้:

typeof(Test).IsSubclassOf(typeof(GenericClassBase))

IsSubclassOf ()

การทดสอบของฉันระบุว่า IsSubclassOf () ไม่ทำงานกับประเภททั่วไปที่ไม่มีพารามิเตอร์เช่น

typeof(GenericClass<>)

ในขณะที่มันจะทำงานกับ

typeof(GenericClass<SomeType>)

ดังนั้นรหัสต่อไปนี้จะทำงานกับ GenericClass <> ใด ๆ โดยสมมติว่าคุณยินดีที่จะทดสอบตาม SomeType:

typeof(Test).IsSubclassOf(typeof(GenericClass<SomeType>))

ครั้งเดียวที่ฉันนึกได้ว่าคุณต้องการทดสอบโดย GenericClass <> อยู่ในสถานการณ์เฟรมเวิร์กปลั๊กอิน


ความคิดเกี่ยวกับตัวดำเนินการ "คือ"

ในขณะออกแบบ C # ไม่อนุญาตให้ใช้พารามิเตอร์แบบไม่มีพารามิเตอร์เพราะโดยพื้นฐานแล้วมันไม่ใช่ประเภท CLR ที่สมบูรณ์ในตอนนั้น ดังนั้นคุณต้องประกาศตัวแปรทั่วไปพร้อมพารามิเตอร์และนั่นคือสาเหตุที่ตัวดำเนินการ "คือ" มีประสิทธิภาพมากสำหรับการทำงานกับวัตถุ อนึ่งตัวดำเนินการ "คือ" ยังไม่สามารถประเมินชนิดทั่วไปที่ไม่มีพารามิเตอร์ได้

ตัวดำเนินการ "is" จะทดสอบห่วงโซ่การสืบทอดทั้งหมดรวมถึงส่วนต่อประสาน

ดังนั้นเมื่อยกตัวอย่างของวัตถุใด ๆ วิธีการต่อไปนี้จะทำการหลอกลวง:

bool IsTypeof<T>(object t)
{
    return (t is T);
}

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

ป.ร. ให้ไว้

var t = new Test();

บรรทัดของรหัสต่อไปนี้จะคืนค่าจริง:

bool test1 = IsTypeof<GenericInterface<SomeType>>(t);

bool test2 = IsTypeof<GenericClass<SomeType>>(t);

bool test3 = IsTypeof<Test>(t);

ในทางตรงกันข้ามถ้าคุณต้องการบางสิ่งบางอย่างที่เฉพาะเจาะจงกับ GenericClass คุณสามารถทำให้เฉพาะเจาะจงมากขึ้นฉันคิดว่าเช่นนี้:

bool IsTypeofGenericClass<SomeType>(object t)
{
    return (t is GenericClass<SomeType>);
}

จากนั้นคุณจะทดสอบเช่นนี้:

bool test1 = IsTypeofGenericClass<SomeType>(t);

2
+1 สำหรับการวิเคราะห์และทดสอบ นอกจากนี้คำตอบของคุณก็มีประโยชน์มากในกรณีของฉัน
Guillermo Gutiérrez

3
ควรสังเกตว่าคอมไพเลอร์มีความสุขอย่างสมบูรณ์กับ. IsSubclassOf (typeof (GenericClass <>)) มันไม่ได้ทำในสิ่งที่คุณต้องการ
2880616

2
แต่จะเกิดอะไรขึ้นถ้า SomeType ไม่รู้จักในเวลารวบรวม
Ryan The Leach

ประเด็นของการอภิปรายทั้งหมดเกี่ยวกับเมื่อคุณมีเพียงTypeวัตถุ
Jonathan Wood

@JonathanWood นั่นคือเหตุผลที่คำตอบของฉันคือการติดต่อกับtypeofผู้ดำเนินการ ตามเอกสาร: "ตัวดำเนินการ typeof ถูกใช้เพื่อรับวัตถุ System.Type สำหรับชนิด"
EnocNRoll - AnandaGopal Pardue

33

ฉันทำงานผ่านตัวอย่างเหล่านี้บางส่วนและพบว่าพวกเขาขาดในบางกรณี รุ่นนี้ทำงานร่วมกับ generics ทุกชนิด: ประเภทอินเตอร์เฟสและคำจำกัดความของประเภทดังกล่าว

public static bool InheritsOrImplements(this Type child, Type parent)
{
    parent = ResolveGenericTypeDefinition(parent);

    var currentChild = child.IsGenericType
                           ? child.GetGenericTypeDefinition()
                           : child;

    while (currentChild != typeof (object))
    {
        if (parent == currentChild || HasAnyInterfaces(parent, currentChild))
            return true;

        currentChild = currentChild.BaseType != null
                       && currentChild.BaseType.IsGenericType
                           ? currentChild.BaseType.GetGenericTypeDefinition()
                           : currentChild.BaseType;

        if (currentChild == null)
            return false;
    }
    return false;
}

private static bool HasAnyInterfaces(Type parent, Type child)
{
    return child.GetInterfaces()
        .Any(childInterface =>
        {
            var currentInterface = childInterface.IsGenericType
                ? childInterface.GetGenericTypeDefinition()
                : childInterface;

            return currentInterface == parent;
        });
}

private static Type ResolveGenericTypeDefinition(Type parent)
{
    var shouldUseGenericType = true;
    if (parent.IsGenericType && parent.GetGenericTypeDefinition() != parent)
        shouldUseGenericType = false;

    if (parent.IsGenericType && shouldUseGenericType)
        parent = parent.GetGenericTypeDefinition();
    return parent;
}

นี่คือการทดสอบหน่วย:

protected interface IFooInterface
{
}

protected interface IGenericFooInterface<T>
{
}

protected class FooBase
{
}

protected class FooImplementor
    : FooBase, IFooInterface
{
}

protected class GenericFooBase
    : FooImplementor, IGenericFooInterface<object>
{

}

protected class GenericFooImplementor<T>
    : FooImplementor, IGenericFooInterface<T>
{
}


[Test]
public void Should_inherit_or_implement_non_generic_interface()
{
    Assert.That(typeof(FooImplementor)
        .InheritsOrImplements(typeof(IFooInterface)), Is.True);
}

[Test]
public void Should_inherit_or_implement_generic_interface()
{
    Assert.That(typeof(GenericFooBase)
        .InheritsOrImplements(typeof(IGenericFooInterface<>)), Is.True);
}

[Test]
public void Should_inherit_or_implement_generic_interface_by_generic_subclass()
{
    Assert.That(typeof(GenericFooImplementor<>)
        .InheritsOrImplements(typeof(IGenericFooInterface<>)), Is.True);
}

[Test]
public void Should_inherit_or_implement_generic_interface_by_generic_subclass_not_caring_about_generic_type_parameter()
{
    Assert.That(new GenericFooImplementor<string>().GetType()
        .InheritsOrImplements(typeof(IGenericFooInterface<>)), Is.True);
}

[Test]
public void Should_not_inherit_or_implement_generic_interface_by_generic_subclass_not_caring_about_generic_type_parameter()
{
    Assert.That(new GenericFooImplementor<string>().GetType()
        .InheritsOrImplements(typeof(IGenericFooInterface<int>)), Is.False);
}

[Test]
public void Should_inherit_or_implement_non_generic_class()
{
    Assert.That(typeof(FooImplementor)
        .InheritsOrImplements(typeof(FooBase)), Is.True);
}

[Test]
public void Should_inherit_or_implement_any_base_type()
{
    Assert.That(typeof(GenericFooImplementor<>)
        .InheritsOrImplements(typeof(FooBase)), Is.True);
}

2
ฉันสับสนเกี่ยวกับวิธีการ ResolveGenericTypeDefinition ตัวแปร "shouldUseGenericType" ได้รับการกำหนดค่าจริงๆ: !parent.IsGenericType || parent.GetGenericTypeDefinition() == parent; ดังนั้นคุณจึงแทนที่ตัวแปรนั้นด้วยการขยายคำสั่ง if: if (parent.IsGenericType && shouldUseGenericType) และคุณจะได้รับ if (parent.IsGenericType && (!parent.IsGenericType || parent.GetGenericTypeDefinition() == parent)) สิ่งที่ลดลงไป if (parent.IsGenericType && parent.GetGenericTypeDefinition() == parent)) parent = parent.GetGenericTypeDefinition();
Michael Blackburn

2
ซึ่งดูเหมือนจะไม่ทำอะไรเลย หากนี่เป็นประเภทค่าที่คล้ายกับint j = 0; if (j is an int && j == 0) { j=0; } ฉันจะได้รับการอ้างอิงของฉันเท่ากับและเท่ากับมูลค่าที่ผสมกันอยู่ไหม ฉันคิดว่ามีเพียงหนึ่งอินสแตนซ์ของหน่วยความจำแต่ละประเภทดังนั้นตัวแปรสองตัวที่ชี้ไปที่ประเภทเดียวกันนั้นอันที่จริงแล้วชี้ไปที่ตำแหน่งเดียวกันในหน่วยความจำ
Michael Blackburn

1
@MichaelBlackburn จุดที่ :) ฉัน refactored สิ่งนี้เป็นเพียงแค่: return parent.IsGenericType? parent.GetGenericTypeDefinition (): parent;
AaronHS

3
ถ้ามันเป็นเช่นเดียวกับผู้ปกครองเพียงแค่ส่งคืน! และเขาเรียกใช้ GetGenericTypeDef หลายครั้งเกินไป มันต้องถูกเรียกเพียงครั้งเดียว
AaronHS

1
ขออภัยฉันจะเพิ่มความคิดเห็นใหม่ด้านล่าง
Menno Deij - van Rijswijk

26

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

public static class ReflexionExtension
{
    public static bool IsSubClassOfGeneric(this Type child, Type parent)
    {
        if (child == parent)
            return false;

        if (child.IsSubclassOf(parent))
            return true;

        var parameters = parent.GetGenericArguments();
        var isParameterLessGeneric = !(parameters != null && parameters.Length > 0 &&
            ((parameters[0].Attributes & TypeAttributes.BeforeFieldInit) == TypeAttributes.BeforeFieldInit));

        while (child != null && child != typeof(object))
        {
            var cur = GetFullTypeDefinition(child);
            if (parent == cur || (isParameterLessGeneric && cur.GetInterfaces().Select(i => GetFullTypeDefinition(i)).Contains(GetFullTypeDefinition(parent))))
                return true;
            else if (!isParameterLessGeneric)
                if (GetFullTypeDefinition(parent) == cur && !cur.IsInterface)
                {
                    if (VerifyGenericArguments(GetFullTypeDefinition(parent), cur))
                        if (VerifyGenericArguments(parent, child))
                            return true;
                }
                else
                    foreach (var item in child.GetInterfaces().Where(i => GetFullTypeDefinition(parent) == GetFullTypeDefinition(i)))
                        if (VerifyGenericArguments(parent, item))
                            return true;

            child = child.BaseType;
        }

        return false;
    }

    private static Type GetFullTypeDefinition(Type type)
    {
        return type.IsGenericType ? type.GetGenericTypeDefinition() : type;
    }

    private static bool VerifyGenericArguments(Type parent, Type child)
    {
        Type[] childArguments = child.GetGenericArguments();
        Type[] parentArguments = parent.GetGenericArguments();
        if (childArguments.Length == parentArguments.Length)
            for (int i = 0; i < childArguments.Length; i++)
                if (childArguments[i].Assembly != parentArguments[i].Assembly || childArguments[i].Name != parentArguments[i].Name || childArguments[i].Namespace != parentArguments[i].Namespace)
                    if (!childArguments[i].IsSubclassOf(parentArguments[i]))
                        return false;

        return true;
    }
}

นี่คือกรณีทดสอบ70 76 ของฉัน:

[TestMethod]
public void IsSubClassOfGenericTest()
{
    Assert.IsTrue(typeof(ChildGeneric).IsSubClassOfGeneric(typeof(BaseGeneric<>)), " 1");
    Assert.IsFalse(typeof(ChildGeneric).IsSubClassOfGeneric(typeof(WrongBaseGeneric<>)), " 2");
    Assert.IsTrue(typeof(ChildGeneric).IsSubClassOfGeneric(typeof(IBaseGeneric<>)), " 3");
    Assert.IsFalse(typeof(ChildGeneric).IsSubClassOfGeneric(typeof(IWrongBaseGeneric<>)), " 4");
    Assert.IsTrue(typeof(IChildGeneric).IsSubClassOfGeneric(typeof(IBaseGeneric<>)), " 5");
    Assert.IsFalse(typeof(IWrongBaseGeneric<>).IsSubClassOfGeneric(typeof(ChildGeneric2<>)), " 6");
    Assert.IsTrue(typeof(ChildGeneric2<>).IsSubClassOfGeneric(typeof(BaseGeneric<>)), " 7");
    Assert.IsTrue(typeof(ChildGeneric2<Class1>).IsSubClassOfGeneric(typeof(BaseGeneric<>)), " 8");
    Assert.IsTrue(typeof(ChildGeneric).IsSubClassOfGeneric(typeof(BaseGeneric<Class1>)), " 9");
    Assert.IsFalse(typeof(ChildGeneric).IsSubClassOfGeneric(typeof(WrongBaseGeneric<Class1>)), "10");
    Assert.IsTrue(typeof(ChildGeneric).IsSubClassOfGeneric(typeof(IBaseGeneric<Class1>)), "11");
    Assert.IsFalse(typeof(ChildGeneric).IsSubClassOfGeneric(typeof(IWrongBaseGeneric<Class1>)), "12");
    Assert.IsTrue(typeof(IChildGeneric).IsSubClassOfGeneric(typeof(IBaseGeneric<Class1>)), "13");
    Assert.IsFalse(typeof(BaseGeneric<Class1>).IsSubClassOfGeneric(typeof(ChildGeneric2<Class1>)), "14");
    Assert.IsTrue(typeof(ChildGeneric2<Class1>).IsSubClassOfGeneric(typeof(BaseGeneric<Class1>)), "15");
    Assert.IsFalse(typeof(ChildGeneric).IsSubClassOfGeneric(typeof(ChildGeneric)), "16");
    Assert.IsFalse(typeof(IChildGeneric).IsSubClassOfGeneric(typeof(IChildGeneric)), "17");
    Assert.IsFalse(typeof(IBaseGeneric<>).IsSubClassOfGeneric(typeof(IChildGeneric2<>)), "18");
    Assert.IsTrue(typeof(IChildGeneric2<>).IsSubClassOfGeneric(typeof(IBaseGeneric<>)), "19");
    Assert.IsTrue(typeof(IChildGeneric2<Class1>).IsSubClassOfGeneric(typeof(IBaseGeneric<>)), "20");
    Assert.IsFalse(typeof(IBaseGeneric<Class1>).IsSubClassOfGeneric(typeof(IChildGeneric2<Class1>)), "21");
    Assert.IsTrue(typeof(IChildGeneric2<Class1>).IsSubClassOfGeneric(typeof(IBaseGeneric<Class1>)), "22");
    Assert.IsFalse(typeof(IBaseGeneric<Class1>).IsSubClassOfGeneric(typeof(BaseGeneric<Class1>)), "23");
    Assert.IsTrue(typeof(BaseGeneric<Class1>).IsSubClassOfGeneric(typeof(IBaseGeneric<Class1>)), "24");
    Assert.IsFalse(typeof(IBaseGeneric<>).IsSubClassOfGeneric(typeof(BaseGeneric<>)), "25");
    Assert.IsTrue(typeof(BaseGeneric<>).IsSubClassOfGeneric(typeof(IBaseGeneric<>)), "26");
    Assert.IsTrue(typeof(BaseGeneric<Class1>).IsSubClassOfGeneric(typeof(IBaseGeneric<>)), "27");
    Assert.IsFalse(typeof(IBaseGeneric<Class1>).IsSubClassOfGeneric(typeof(IBaseGeneric<Class1>)), "28");
    Assert.IsTrue(typeof(BaseGeneric2<Class1>).IsSubClassOfGeneric(typeof(IBaseGeneric<Class1>)), "29");
    Assert.IsFalse(typeof(IBaseGeneric<>).IsSubClassOfGeneric(typeof(BaseGeneric2<>)), "30");
    Assert.IsTrue(typeof(BaseGeneric2<>).IsSubClassOfGeneric(typeof(IBaseGeneric<>)), "31");
    Assert.IsTrue(typeof(BaseGeneric2<Class1>).IsSubClassOfGeneric(typeof(IBaseGeneric<>)), "32");
    Assert.IsTrue(typeof(ChildGenericA).IsSubClassOfGeneric(typeof(BaseGenericA<,>)), "33");
    Assert.IsFalse(typeof(ChildGenericA).IsSubClassOfGeneric(typeof(WrongBaseGenericA<,>)), "34");
    Assert.IsTrue(typeof(ChildGenericA).IsSubClassOfGeneric(typeof(IBaseGenericA<,>)), "35");
    Assert.IsFalse(typeof(ChildGenericA).IsSubClassOfGeneric(typeof(IWrongBaseGenericA<,>)), "36");
    Assert.IsTrue(typeof(IChildGenericA).IsSubClassOfGeneric(typeof(IBaseGenericA<,>)), "37");
    Assert.IsFalse(typeof(IWrongBaseGenericA<,>).IsSubClassOfGeneric(typeof(ChildGenericA2<,>)), "38");
    Assert.IsTrue(typeof(ChildGenericA2<,>).IsSubClassOfGeneric(typeof(BaseGenericA<,>)), "39");
    Assert.IsTrue(typeof(ChildGenericA2<ClassA, ClassB>).IsSubClassOfGeneric(typeof(BaseGenericA<,>)), "40");
    Assert.IsTrue(typeof(ChildGenericA).IsSubClassOfGeneric(typeof(BaseGenericA<ClassA, ClassB>)), "41");
    Assert.IsFalse(typeof(ChildGenericA).IsSubClassOfGeneric(typeof(WrongBaseGenericA<ClassA, ClassB>)), "42");
    Assert.IsTrue(typeof(ChildGenericA).IsSubClassOfGeneric(typeof(IBaseGenericA<ClassA, ClassB>)), "43");
    Assert.IsFalse(typeof(ChildGenericA).IsSubClassOfGeneric(typeof(IWrongBaseGenericA<ClassA, ClassB>)), "44");
    Assert.IsTrue(typeof(IChildGenericA).IsSubClassOfGeneric(typeof(IBaseGenericA<ClassA, ClassB>)), "45");
    Assert.IsFalse(typeof(BaseGenericA<ClassA, ClassB>).IsSubClassOfGeneric(typeof(ChildGenericA2<ClassA, ClassB>)), "46");
    Assert.IsTrue(typeof(ChildGenericA2<ClassA, ClassB>).IsSubClassOfGeneric(typeof(BaseGenericA<ClassA, ClassB>)), "47");
    Assert.IsFalse(typeof(ChildGenericA).IsSubClassOfGeneric(typeof(ChildGenericA)), "48");
    Assert.IsFalse(typeof(IChildGenericA).IsSubClassOfGeneric(typeof(IChildGenericA)), "49");
    Assert.IsFalse(typeof(IBaseGenericA<,>).IsSubClassOfGeneric(typeof(IChildGenericA2<,>)), "50");
    Assert.IsTrue(typeof(IChildGenericA2<,>).IsSubClassOfGeneric(typeof(IBaseGenericA<,>)), "51");
    Assert.IsTrue(typeof(IChildGenericA2<ClassA, ClassB>).IsSubClassOfGeneric(typeof(IBaseGenericA<,>)), "52");
    Assert.IsFalse(typeof(IBaseGenericA<ClassA, ClassB>).IsSubClassOfGeneric(typeof(IChildGenericA2<ClassA, ClassB>)), "53");
    Assert.IsTrue(typeof(IChildGenericA2<ClassA, ClassB>).IsSubClassOfGeneric(typeof(IBaseGenericA<ClassA, ClassB>)), "54");
    Assert.IsFalse(typeof(IBaseGenericA<ClassA, ClassB>).IsSubClassOfGeneric(typeof(BaseGenericA<ClassA, ClassB>)), "55");
    Assert.IsTrue(typeof(BaseGenericA<ClassA, ClassB>).IsSubClassOfGeneric(typeof(IBaseGenericA<ClassA, ClassB>)), "56");
    Assert.IsFalse(typeof(IBaseGenericA<,>).IsSubClassOfGeneric(typeof(BaseGenericA<,>)), "57");
    Assert.IsTrue(typeof(BaseGenericA<,>).IsSubClassOfGeneric(typeof(IBaseGenericA<,>)), "58");
    Assert.IsTrue(typeof(BaseGenericA<ClassA, ClassB>).IsSubClassOfGeneric(typeof(IBaseGenericA<,>)), "59");
    Assert.IsFalse(typeof(IBaseGenericA<ClassA, ClassB>).IsSubClassOfGeneric(typeof(IBaseGenericA<ClassA, ClassB>)), "60");
    Assert.IsTrue(typeof(BaseGenericA2<ClassA, ClassB>).IsSubClassOfGeneric(typeof(IBaseGenericA<ClassA, ClassB>)), "61");
    Assert.IsFalse(typeof(IBaseGenericA<,>).IsSubClassOfGeneric(typeof(BaseGenericA2<,>)), "62");
    Assert.IsTrue(typeof(BaseGenericA2<,>).IsSubClassOfGeneric(typeof(IBaseGenericA<,>)), "63");
    Assert.IsTrue(typeof(BaseGenericA2<ClassA, ClassB>).IsSubClassOfGeneric(typeof(IBaseGenericA<,>)), "64");
    Assert.IsFalse(typeof(BaseGenericA2<ClassB, ClassA>).IsSubClassOfGeneric(typeof(IBaseGenericA<ClassA, ClassB>)), "65");
    Assert.IsFalse(typeof(BaseGenericA<ClassB, ClassA>).IsSubClassOfGeneric(typeof(ChildGenericA2<ClassA, ClassB>)), "66");
    Assert.IsFalse(typeof(BaseGenericA2<ClassB, ClassA>).IsSubClassOfGeneric(typeof(BaseGenericA<ClassA, ClassB>)), "67");
    Assert.IsTrue(typeof(ChildGenericA3<ClassA, ClassB>).IsSubClassOfGeneric(typeof(BaseGenericB<ClassA, ClassB, ClassC>)), "68");
    Assert.IsTrue(typeof(ChildGenericA4<ClassA, ClassB>).IsSubClassOfGeneric(typeof(IBaseGenericB<ClassA, ClassB, ClassC>)), "69");
    Assert.IsFalse(typeof(ChildGenericA3<ClassB, ClassA>).IsSubClassOfGeneric(typeof(BaseGenericB<ClassA, ClassB, ClassC>)), "68-2");
    Assert.IsTrue(typeof(ChildGenericA3<ClassA, ClassB2>).IsSubClassOfGeneric(typeof(BaseGenericB<ClassA, ClassB, ClassC>)), "68-3");
    Assert.IsFalse(typeof(ChildGenericA3<ClassB2, ClassA>).IsSubClassOfGeneric(typeof(BaseGenericB<ClassA, ClassB, ClassC>)), "68-4");
    Assert.IsFalse(typeof(ChildGenericA4<ClassB, ClassA>).IsSubClassOfGeneric(typeof(IBaseGenericB<ClassA, ClassB, ClassC>)), "69-2");
    Assert.IsTrue(typeof(ChildGenericA4<ClassA, ClassB2>).IsSubClassOfGeneric(typeof(IBaseGenericB<ClassA, ClassB, ClassC>)), "69-3");
    Assert.IsFalse(typeof(ChildGenericA4<ClassB2, ClassA>).IsSubClassOfGeneric(typeof(IBaseGenericB<ClassA, ClassB, ClassC>)), "69-4");
    Assert.IsFalse(typeof(bool).IsSubClassOfGeneric(typeof(IBaseGenericB<ClassA, ClassB, ClassC>)), "70");
}

คลาสและอินเทอร์เฟซสำหรับการทดสอบ:

public class Class1 { }
public class BaseGeneric<T> : IBaseGeneric<T> { }
public class BaseGeneric2<T> : IBaseGeneric<T>, IInterfaceBidon { }
public interface IBaseGeneric<T> { }
public class ChildGeneric : BaseGeneric<Class1> { }
public interface IChildGeneric : IBaseGeneric<Class1> { }
public class ChildGeneric2<Class1> : BaseGeneric<Class1> { }
public interface IChildGeneric2<Class1> : IBaseGeneric<Class1> { }

public class WrongBaseGeneric<T> { }
public interface IWrongBaseGeneric<T> { }

public interface IInterfaceBidon { }

public class ClassA { }
public class ClassB { }
public class ClassC { }
public class ClassB2 : ClassB { }
public class BaseGenericA<T, U> : IBaseGenericA<T, U> { }
public class BaseGenericB<T, U, V> { }
public interface IBaseGenericB<ClassA, ClassB, ClassC> { }
public class BaseGenericA2<T, U> : IBaseGenericA<T, U>, IInterfaceBidonA { }
public interface IBaseGenericA<T, U> { }
public class ChildGenericA : BaseGenericA<ClassA, ClassB> { }
public interface IChildGenericA : IBaseGenericA<ClassA, ClassB> { }
public class ChildGenericA2<ClassA, ClassB> : BaseGenericA<ClassA, ClassB> { }
public class ChildGenericA3<ClassA, ClassB> : BaseGenericB<ClassA, ClassB, ClassC> { }
public class ChildGenericA4<ClassA, ClassB> : IBaseGenericB<ClassA, ClassB, ClassC> { }
public interface IChildGenericA2<ClassA, ClassB> : IBaseGenericA<ClassA, ClassB> { }

public class WrongBaseGenericA<T, U> { }
public interface IWrongBaseGenericA<T, U> { }

public interface IInterfaceBidonA { }

4
นี่เป็นทางออกเดียวที่ได้ผลสำหรับฉัน ฉันไม่พบโซลูชันอื่นใดที่ทำงานกับคลาสที่มีพารามิเตอร์หลายประเภท ขอบคุณ.
Connor Clark

1
ฉันชื่นชมการโพสต์กรณีทดสอบทั้งหมดเหล่านี้ ฉันคิดว่ากรณี 68 และ 69 ควรเป็นเท็จแทนที่จะเป็นจริงเนื่องจากคุณมี ClassB, ClassA ด้านซ้ายและ ClassA, ClassB ด้านขวา
Grax32

คุณพูดถูก @Grax ฉันไม่มีเวลาทำการแก้ไข แต่ฉันจะอัปเดตโพสต์ทันทีที่เสร็จสิ้น ฉันคิดว่าการแก้ไขจะต้องอยู่ในวิธี "VerifyGenericArguments"
Xav987

1
@ Grax: ฉันพบเวลาทำการแก้ไข ฉันเพิ่มคลาส ClassB2 ฉันเปลี่ยน VerifyGenericArguments และฉันเพิ่มการควบคุมที่การเรียก VerifyGenericArguments ฉันยังแก้ไขเคส 68 และ 69 และเพิ่ม 68-2, 68-3, 68-4, 69-2, 69-3 และ 69-4
Xav987

1
ขอบคุณครับ +1 สำหรับโซลูชันการทำงานและกรณีทดสอบจำนวนมหาศาล (อย่างมากสำหรับฉันอย่างน้อย)
Eldoïr

10

รหัสของ JaredPar ใช้งานได้ แต่สำหรับการสืบทอดระดับเดียวเท่านั้น สำหรับการสืบทอดระดับไม่ จำกัด ให้ใช้รหัสต่อไปนี้

public bool IsTypeDerivedFromGenericType(Type typeToCheck, Type genericType)
{
    if (typeToCheck == typeof(object))
    {
        return false;
    }
    else if (typeToCheck == null)
    {
        return false;
    }
    else if (typeToCheck.IsGenericType && typeToCheck.GetGenericTypeDefinition() == genericType)
    {
        return true;
    }
    else
    {
        return IsTypeDerivedFromGenericType(typeToCheck.BaseType, genericType);
    }
}

4
รหัสwhileใน JaredPar ครอบคลุมระดับไม่ จำกัด
Jay

@jay ... และหลีกเลี่ยงการเรียกซ้ำ
Marc L.

1
@MarcL วิธีนี้ใช้การเรียกซ้ำแบบหางดังนั้นจึงควรเป็นเรื่องเล็กน้อยสำหรับคอมไพเลอร์เพื่อปรับการเรียกซ้ำให้เหมาะสม
Darhuuk

10

นี่เป็นวิธีการเล็กน้อยที่ฉันสร้างขึ้นเพื่อตรวจสอบว่าวัตถุนั้นได้มาจากประเภทที่เฉพาะเจาะจง ใช้งานได้ดีสำหรับฉัน!

internal static bool IsDerivativeOf(this Type t, Type typeToCompare)
{
    if (t == null) throw new NullReferenceException();
    if (t.BaseType == null) return false;

    if (t.BaseType == typeToCompare) return true;
    else return t.BaseType.IsDerivativeOf(typeToCompare);
}

7

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

ตัวอย่างเช่นในคำถามสามารถทดสอบกับอินเตอร์เฟสทั่วไปเช่นเดียวกับคลาสทั่วไป ชนิดที่ส่งคืนสามารถใช้กับGetGenericArgumentsเพื่อกำหนดว่าอาร์กิวเมนต์ชนิดทั่วไปคือ "SomeType"

/// <summary>
/// Checks whether this type has the specified definition in its ancestry.
/// </summary>   
public static bool HasGenericDefinition(this Type type, Type definition)
{
    return GetTypeWithGenericDefinition(type, definition) != null;
}

/// <summary>
/// Returns the actual type implementing the specified definition from the
/// ancestry of the type, if available. Else, null.
/// </summary>
public static Type GetTypeWithGenericDefinition(this Type type, Type definition)
{
    if (type == null)
        throw new ArgumentNullException("type");
    if (definition == null)
        throw new ArgumentNullException("definition");
    if (!definition.IsGenericTypeDefinition)
        throw new ArgumentException(
            "The definition needs to be a GenericTypeDefinition", "definition");

    if (definition.IsInterface)
        foreach (var interfaceType in type.GetInterfaces())
            if (interfaceType.IsGenericType
                && interfaceType.GetGenericTypeDefinition() == definition)
                return interfaceType;

    for (Type t = type; t != null; t = t.BaseType)
        if (t.IsGenericType && t.GetGenericTypeDefinition() == definition)
            return t;

    return null;
}

โพสต์ที่ดี! ยินดีที่จะแยกข้อกังวลออกเป็นสองวิธี
Wiebe Tijsma

4

การสร้างคำตอบที่ยอดเยี่ยมข้างต้นโดย fir3rpho3nixx และ David Schmitt ฉันได้แก้ไขโค้ดและเพิ่มการทดสอบ ShouldInheritOrImplementTypedGenericInterface (สุดท้าย)

    /// <summary>
    /// Find out if a child type implements or inherits from the parent type.
    /// The parent type can be an interface or a concrete class, generic or non-generic.
    /// </summary>
    /// <param name="child"></param>
    /// <param name="parent"></param>
    /// <returns></returns>
    public static bool InheritsOrImplements(this Type child, Type parent)
    {
        var currentChild = parent.IsGenericTypeDefinition && child.IsGenericType ? child.GetGenericTypeDefinition() : child;

        while (currentChild != typeof(object))
        {
            if (parent == currentChild || HasAnyInterfaces(parent, currentChild))
                return true;

            currentChild = currentChild.BaseType != null && parent.IsGenericTypeDefinition && currentChild.BaseType.IsGenericType
                                ? currentChild.BaseType.GetGenericTypeDefinition()
                                : currentChild.BaseType;

            if (currentChild == null)
                return false;
        }
        return false;
    }

    private static bool HasAnyInterfaces(Type parent, Type child)
    {
        return child.GetInterfaces().Any(childInterface =>
            {
                var currentInterface = parent.IsGenericTypeDefinition && childInterface.IsGenericType
                    ? childInterface.GetGenericTypeDefinition()
                    : childInterface;

                return currentInterface == parent;
            });

    }

    [Test]
    public void ShouldInheritOrImplementNonGenericInterface()
    {
        Assert.That(typeof(FooImplementor)
            .InheritsOrImplements(typeof(IFooInterface)), Is.True);
    }

    [Test]
    public void ShouldInheritOrImplementGenericInterface()
    {
        Assert.That(typeof(GenericFooBase)
            .InheritsOrImplements(typeof(IGenericFooInterface<>)), Is.True);
    }

    [Test]
    public void ShouldInheritOrImplementGenericInterfaceByGenericSubclass()
    {
        Assert.That(typeof(GenericFooImplementor<>)
            .InheritsOrImplements(typeof(IGenericFooInterface<>)), Is.True);
    }

    [Test]
    public void ShouldInheritOrImplementGenericInterfaceByGenericSubclassNotCaringAboutGenericTypeParameter()
    {
        Assert.That(new GenericFooImplementor<string>().GetType()
            .InheritsOrImplements(typeof(IGenericFooInterface<>)), Is.True);
    }

    [Test]
    public void ShouldNotInheritOrImplementGenericInterfaceByGenericSubclassNotCaringAboutGenericTypeParameter()
    {
        Assert.That(new GenericFooImplementor<string>().GetType()
            .InheritsOrImplements(typeof(IGenericFooInterface<int>)), Is.False);
    }

    [Test]
    public void ShouldInheritOrImplementNonGenericClass()
    {
        Assert.That(typeof(FooImplementor)
            .InheritsOrImplements(typeof(FooBase)), Is.True);
    }

    [Test]
    public void ShouldInheritOrImplementAnyBaseType()
    {
        Assert.That(typeof(GenericFooImplementor<>)
            .InheritsOrImplements(typeof(FooBase)), Is.True);
    }

    [Test]
    public void ShouldInheritOrImplementTypedGenericInterface()
    {
        GenericFooImplementor<int> obj = new GenericFooImplementor<int>();
        Type t = obj.GetType();

        Assert.IsTrue(t.InheritsOrImplements(typeof(IGenericFooInterface<int>)));
        Assert.IsFalse(t.InheritsOrImplements(typeof(IGenericFooInterface<String>)));
    } 

4

ทั้งหมดนี้สามารถทำได้อย่างง่ายดายด้วย linq นี่จะหาชนิดใด ๆ ที่เป็นคลาสย่อยของคลาสพื้นฐานทั่วไป GenericBaseType

    IEnumerable<Type> allTypes = Assembly.GetExecutingAssembly().GetTypes();

    IEnumerable<Type> mySubclasses = allTypes.Where(t => t.BaseType != null 
                                                            && t.BaseType.IsGenericType
                                                            && t.BaseType.GetGenericTypeDefinition() == typeof(GenericBaseType<,>));

นี่เป็นทางออกเดียวที่ได้ผลสำหรับฉัน เรียบง่ายและสง่างาม ขอบคุณ.
Silkfire

4

วิธีแก้ปัญหาง่ายๆ: เพียงแค่สร้างและเพิ่มส่วนต่อประสานที่สองที่ไม่ใช่แบบทั่วไปให้กับคลาสทั่วไป:

public interface IGenericClass
{
}

public class GenericClass<T> : GenericInterface<T>, IGenericClass
{
}

แล้วก็ตรวจสอบว่าในทางใด ๆ ที่คุณชอบใช้is, as, IsAssignableFromฯลฯ

if (thing is IGenericClass)
{
    // Do work
{

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


1
อย่างไรก็ตามเพียงแค่ตรวจสอบว่าสิ่งที่เป็นประเภทIGenericClassจะไม่รับประกันคุณว่าGenericClassหรือGenericInterfaceมีการขยายหรือนำไปใช้จริง ซึ่งหมายความว่าคอมไพเลอร์ของคุณจะไม่อนุญาตให้คุณเข้าถึงสมาชิกของคลาสทั่วไป
B12Toaster

4

เพิ่มคำตอบของ @ jaredpar นี่คือสิ่งที่ฉันใช้เพื่อตรวจสอบอินเทอร์เฟซ:

public static bool IsImplementerOfRawGeneric(this Type type, Type toCheck)
{
    if (toCheck.GetTypeInfo().IsClass)
    {
        return false;
    }

    return type.GetInterfaces().Any(interfaceType =>
    {
        var current = interfaceType.GetTypeInfo().IsGenericType ?
                    interfaceType.GetGenericTypeDefinition() : interfaceType;
        return current == toCheck;
    });
}

public static bool IsSubTypeOfRawGeneric(this Type type, Type toCheck)
{
    return type.IsInterface ?
          IsImplementerOfRawGeneric(type, toCheck)
        : IsSubclassOfRawGeneric(type, toCheck);
}

Ex:

Console.WriteLine(typeof(IList<>).IsSubTypeOfRawGeneric(typeof(IList<int>))); // true

นอกจากนี้ที่ดี ให้ทั้งคุณและ jaredpar upvote ความคิดเห็นเดียวของฉันคือคุณกลับตำแหน่งประเภท (จากคำตอบของ jaredpar) เนื่องจากวิธีการขยาย ฉันลบมันเป็นวิธีส่วนขยายและมันก็โยนฉันออกไปเล็กน้อย ไม่ใช่ปัญหาของคุณ แต่เป็นของฉัน แค่ต้องการให้คนต่อไปหัวขึ้น ขอบคุณอีกครั้ง.
Tony

@ Tony ขอบคุณสำหรับเคล็ดลับ แต่ครั้งต่อไปอัปเดตคำตอบ
johnny 5

@vexe การทดสอบเป็นสิ่งสำคัญที่คำตอบดั้งเดิมของคุณจะใช้งานไม่ได้เพราะคุณทดสอบในอินเทอร์เฟซ ประการที่สองคุณจะสูญเสียพลังในการประมวลผลโดยการใช้ฟังก์ชั่นนี้โดยไม่คำนึงถึงประเภท
johnny 5

2

JaredPar,

สิ่งนี้ไม่ได้ผลสำหรับฉันหากฉันส่ง typeof (type <>) เป็น toCheck นี่คือสิ่งที่ฉันเปลี่ยนไป

static bool IsSubclassOfRawGeneric(Type generic, Type toCheck) {
    while (toCheck != typeof(object)) {
        var cur = toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck;
          if (cur.IsGenericType && generic.GetGenericTypeDefinition() == cur.GetGenericTypeDefinition()) {
            return true;
        }
        toCheck = toCheck.BaseType;
    }
    return false;
}

วิธีการแก้ปัญหาของ JaredPar แน่นอนทำงานให้เป็นtypeof(type<>) toCheckนอกจากนี้คุณต้องตรวจสอบโมฆะเช่นเดียวกับในวิธีการแก้ปัญหาของ JaredPar นอกจากนี้ฉันไม่ทราบว่าสิ่งที่คุณประสบความสำเร็จโดยแทนที่cur == genericในวิธีการแก้ปัญหาของเขาโดยcur.IsGenericType && generic.GetGenericTypeDefinition() == cur.GetGenericTypeDefinition()นอกเหนือจากการ จำกัด วิธีการของคุณให้ทำงานเฉพาะสำหรับประเภททั่วไป กล่าวอีกนัยหนึ่งสิ่งนี้ล้มเหลวโดยมีข้อยกเว้น:IsSubclassOfRawGeneric(typeof(MyClass), typeof(MyClass<>))
nawfal

2

@EnocNRoll - คำตอบของ Ananda Gopal นั้นน่าสนใจ แต่ในกรณีที่อินสแตนซ์นั้นไม่ได้สร้างอินสแตนซ์ไว้ล่วงหน้าหรือคุณต้องการตรวจสอบกับนิยามประเภททั่วไปฉันอยากจะแนะนำวิธีนี้:

public static bool TypeIs(this Type x, Type d) {
    if(null==d) {
        return false;
    }

    for(var c = x; null!=c; c=c.BaseType) {
        var a = c.GetInterfaces();

        for(var i = a.Length; i-->=0;) {
            var t = i<0 ? c : a[i];

            if(t==d||t.IsGenericType&&t.GetGenericTypeDefinition()==d) {
                return true;
            }
        }
    }

    return false;
}

และใช้มันเช่น:

var b = typeof(char[]).TypeIs(typeof(IList<>)); // true

มีสี่กรณีมีเงื่อนไขเมื่อทั้งสองt(ได้รับการทดสอบ) และdประเภททั่วไปและกรณีที่สองถูกปกคลุมไปด้วยt==dซึ่งเป็น(1) ค่าtมิได้dเป็นคำนิยามทั่วไปหรือ(2) ทั้งสองของพวกเขาเป็นคำจำกัดความทั่วไป กรณีที่เหลือเป็นหนึ่งในนั้นคือคำจำกัดความทั่วไปเฉพาะเมื่อdเป็นคำจำกัดความทั่วไปแล้วเรามีโอกาสที่จะพูดว่าa tเป็นdแต่ไม่ใช่ในทางกลับกัน

ควรทำงานกับคลาสหรืออินเทอร์เฟซที่คุณต้องการทดสอบและส่งคืนสิ่งที่ราวกับว่าคุณทดสอบอินสแตนซ์ของชนิดนั้นกับตัวisดำเนินการ


0
Type _type = myclass.GetType();
PropertyInfo[] _propertyInfos = _type.GetProperties();
Boolean _test = _propertyInfos[0].PropertyType.GetGenericTypeDefinition() 
== typeof(List<>);

0

คุณสามารถลองใช้ส่วนขยายนี้

    public static bool IsSubClassOfGenericClass(this Type type, Type genericClass,Type t)
    {
        return type.IsSubclassOf(genericClass.MakeGenericType(new[] { t }));
    }

0

สายไปที่เกมนี้ ... ฉันยังมีการเปลี่ยนแปลงคำตอบของ JarodPar อีก

นี่คือ Type.IsSubClassOf (ประเภท) ความอนุเคราะห์ของตัวสะท้อนแสง:

    public virtual bool IsSubclassOf(Type c)
    {
        Type baseType = this;
        if (!(baseType == c))
        {
            while (baseType != null)
            {
                if (baseType == c)
                {
                    return true;
                }
                baseType = baseType.BaseType;
            }
            return false;
        }
        return false;
    }

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

    public static bool IsExtension(this Type thisType, Type potentialSuperType)
    {
        //
        // protect ya neck
        //
        if (thisType == null || potentialSuperType == null || thisType == potentialSuperType) return false;

        //
        // don't need to traverse inheritance for interface extension, so check/do these first
        //
        if (potentialSuperType.IsInterface)
        {
            foreach (var interfaceType in thisType.GetInterfaces())
            {
                var tempType = interfaceType.IsGenericType ? interfaceType.GetGenericTypeDefinition() : interfaceType;

                if (tempType == potentialSuperType)
                {
                    return true;
                }
            }
        }

        //
        // do the concrete type checks, iterating up the inheritance chain, as in orignal
        //
        while (thisType != null && thisType != typeof(object))
        {
            var cur = thisType.IsGenericType ? thisType.GetGenericTypeDefinition() : thisType;

            if (potentialSuperType == cur)
            {
                return true;
            }

            thisType = thisType.BaseType;
        }
        return false;
    }

โดยทั่วไปนี่เป็นเพียงวิธีการขยายไปยัง System.Type - ฉันทำสิ่งนี้โดยตั้งใจ จำกัด ประเภท "thisType" เป็นประเภทคอนกรีตเนื่องจากการใช้งานทันทีของฉันคือการสืบค้น LINQ "โดยที่" predicates ต่อวัตถุประเภท ฉันแน่ใจว่าทุกคนที่ชาญฉลาดของคุณอาจมีวิธีการแบบคงที่ที่มีประสิทธิภาพและอเนกประสงค์ถ้าคุณต้องการ :) รหัสทำบางสิ่งที่รหัสคำตอบไม่ได้

  1. เปิดมันขึ้นอยู่กับ "ส่วนขยาย" ทั่วไป - ฉันกำลังพิจารณาการสืบทอด (คิดว่าคลาส) รวมถึงการนำไปใช้ (ส่วนต่อประสาน); ชื่อเมธอดและพารามิเตอร์ถูกเปลี่ยนเพื่อให้สะท้อนสิ่งนี้ได้ดียิ่งขึ้น
  2. อินพุตการตรวจสอบความถูกต้องเป็นโมฆะ (meah)
  3. อินพุตประเภทเดียวกัน (คลาสไม่สามารถขยายตัวเองได้)
  4. การลัดวงจรถ้า Type in question เป็นอินเตอร์เฟส; เนื่องจาก GetInterfaces () ส่งคืนอินเทอร์เฟซที่ถูกนำมาใช้ทั้งหมด (แม้จะใช้ในซูเปอร์คลาส) คุณก็สามารถวนรอบคอลเลกชันนั้นโดยไม่ต้องปีนต้นไม้มรดก

ส่วนที่เหลือนั้นเป็นเหมือนกับรหัสของ JaredPar

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