รับประเภท T จาก IEnumerable <T>


106

มีวิธีดึงข้อมูลประเภทTจากการIEnumerable<T>สะท้อนหรือไม่?

เช่น

ฉันมีIEnumerable<Child>ข้อมูลตัวแปร ฉันต้องการดึงข้อมูลประเภทของเด็กผ่านการไตร่ตรอง


1
ในบริบทใด? IEnumerable <T> นี้คืออะไร? มันเป็นอินสแตนซ์ของวัตถุที่ส่งเป็นอาร์กิวเมนต์หรือไม่? หรืออะไร?
Mehrdad Afshari

คำตอบ:


142
IEnumerable<T> myEnumerable;
Type type = myEnumerable.GetType().GetGenericArguments()[0]; 

ดังนั้น

IEnumerable<string> strings = new List<string>();
Console.WriteLine(strings.GetType().GetGenericArguments()[0]);

System.Stringพิมพ์

ดูMSDNสำหรับType.GetGenericArguments.

แก้ไข:ฉันเชื่อว่าสิ่งนี้จะช่วยแก้ปัญหาในความคิดเห็น:

// returns an enumeration of T where o : IEnumerable<T>
public IEnumerable<Type> GetGenericIEnumerables(object o) {
    return o.GetType()
            .GetInterfaces()
            .Where(t => t.IsGenericType
                && t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
            .Select(t => t.GetGenericArguments()[0]);
}

อ็อบเจ็กต์บางตัวใช้มากกว่าหนึ่งทั่วไปIEnumerableดังนั้นจึงจำเป็นต้องส่งคืนการแจงนับ

แก้ไข:แม้ว่าฉันต้องบอกว่ามันเป็นความคิดที่แย่มากสำหรับชั้นเรียนที่จะนำไปใช้IEnumerable<T>มากกว่าหนึ่งTชั้น


หรือที่แย่กว่านั้นคือเขียน method ที่มีผลตอบแทนและพยายามเรียก GetType บนตัวแปรที่สร้างด้วยวิธีนี้ มันจะบอกคุณว่าไม่ใช่เหตุการณ์ประเภททั่วไป ดังนั้นโดยพื้นฐานแล้วไม่มีวิธีสากลในการรับ T เนื่องจากตัวแปรอินสแตนซ์ประเภท IEnumerable <T>
Darin Dimitrov

1
หรือลองใช้คลาส MyClass: IEnumerable <int> {} คลาสนี้ไม่มีอินเทอร์เฟซทั่วไป
Stefan Steinegger

1
ทำไมใคร ๆ ก็หันไปหาข้อโต้แย้งทั่วไปแล้วคว้าประเภทจากตัวสร้างดัชนี นั่นเป็นเพียงการขอให้หายนะโดยเฉพาะอย่างยิ่งเมื่อภาษารองรับ typeof (T) เช่น @amsprich แนะนำในคำตอบของเขา / เธอซึ่งสามารถใช้ได้กับประเภททั่วไปหรือประเภทที่รู้จัก ...
Robert Petz

นี้ล้มเหลวอย่างน่าสังเวชเมื่อใช้กับคำสั่ง LINQ - อาร์กิวเมนต์ทั่วไปครั้งแรกของ WhereSelectEnumerableIterator เป็นไม่ได้ คุณได้รับอาร์กิวเมนต์ทั่วไปของวัตถุที่อยู่ข้างใต้ไม่ใช่อินเทอร์เฟซเอง
Pxtl

myEnumerable.GetType () GetGenericArguments () [0] ให้คุณสมบัติ FullName แก่คุณซึ่งจะบอกคุณถึง namespace.classname หากคุณกำลังมองหาเฉพาะชื่อคลาสให้ใช้ myEnumerable.GetType (). GetGenericArguments () [0]
.Name

38

ฉันแค่สร้างวิธีการขยาย สิ่งนี้ใช้ได้กับทุกสิ่งที่ฉันโยนไป

public static Type GetItemType<T>(this IEnumerable<T> enumerable)
{
    return typeof(T);
}

6
จะไม่ทำงานหากการอ้างอิงเวลาคอมไพล์ของคุณเป็นเพียงวัตถุประเภท
Stijn Van Antwerpen

27

ฉันมีปัญหาที่คล้ายกัน คำตอบที่เลือกใช้ได้กับอินสแตนซ์จริง ในกรณีของฉันฉันมีเพียงประเภท (จาก a PropertyInfo)

คำตอบที่เลือกล้มเหลวเมื่อชนิดที่ตัวเองไม่ได้การดำเนินการของtypeof(IEnumerable<T>)IEnumerable<T>

สำหรับกรณีนี้การทำงานดังต่อไปนี้:

public static Type GetAnyElementType(Type type)
{
   // Type is Array
   // short-circuit if you expect lots of arrays 
   if (type.IsArray)
      return type.GetElementType();

   // type is IEnumerable<T>;
   if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof (IEnumerable<>))
      return type.GetGenericArguments()[0];

   // type implements/extends IEnumerable<T>;
   var enumType = type.GetInterfaces()
                           .Where(t => t.IsGenericType && 
                                  t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
                           .Select(t => t.GenericTypeArguments[0]).FirstOrDefault();
   return enumType ?? type;
}

บันทึกวันของฉัน สำหรับกรณีของฉันฉันได้เพิ่มคำสั่ง if แยกต่างหากเพื่อจัดการกับสตริงเนื่องจากมันใช้ IEnumerable <char>
Edmund P Charumbira

Type.GenericTypeArguments- สำหรับเวอร์ชัน dotNet FrameWork เท่านั้น> = 4.5 มิฉะนั้น - ใช้Type.GetGenericArgumentsแทน
КоеКто

20

หากคุณรู้จักIEnumerable<T>(ผ่านทางยาสามัญ) ก็typeof(T)ควรใช้งานได้ มิฉะนั้น (สำหรับobjectหรือไม่ใช่แบบทั่วไปIEnumerable) ให้ตรวจสอบอินเทอร์เฟซที่ใช้งาน:

        object obj = new string[] { "abc", "def" };
        Type type = null;
        foreach (Type iType in obj.GetType().GetInterfaces())
        {
            if (iType.IsGenericType && iType.GetGenericTypeDefinition()
                == typeof(IEnumerable<>))
            {
                type = iType.GetGenericArguments()[0];
                break;
            }
        }
        if (type != null) Console.WriteLine(type);

3
อ็อบเจ็กต์บางตัวใช้ IEnumerable ทั่วไปมากกว่าหนึ่งตัว
สัน

5
@ Jason - และในกรณีดังกล่าวคำถาม "find the T" เป็นคำถามที่น่าสงสัยอยู่แล้ว ฉันทำอะไรไม่ได้เลย ...
Marc Gravell

gotcha เล็ก ๆ สำหรับใครก็ตามที่พยายามใช้สิ่งนี้กับType typeพารามิเตอร์แทนที่จะเป็นobject objพารามิเตอร์: คุณไม่สามารถแทนที่obj.GetType()ด้วยtypeเพราะถ้าคุณส่งผ่านtypeof(IEnumerable<T>)คุณจะไม่ได้อะไรเลย เพื่อให้ได้รอบนี้ทดสอบtypeตัวเองเพื่อดูว่าเป็นIEnumerable<>อินเทอร์เฟซทั่วไปหรือไม่
Ian Mercer

8

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

  Type GetItemType(object someCollection)
  {
    var type = someCollection.GetType();
    var ienum = type.GetInterface(typeof(IEnumerable<>).Name);
    return ienum != null
      ? ienum.GetGenericArguments()[0]
      : null;
  }

นี่คือซับเดียวที่ทำทั้งหมดนี้โดยใช้ตัวดำเนินการเงื่อนไขว่าง: someCollection.GetType().GetInterface(typeof(IEnumerable<>).Name)?.GetGenericArguments()?.FirstOrDefault()
Mass Dot Net

2

เพียงแค่ใช้ typeof(T)

แก้ไข: หรือใช้. GetType (). GetGenericParameter () บนวัตถุที่สร้างอินสแตนซ์หากคุณไม่มี T.


คุณไม่มี T.
jason

จริงซึ่งในกรณีนี้คุณสามารถใช้. GetType () ฉันจะแก้ไขคำตอบของฉัน
บังเหียน

2

ทางเลือกสำหรับสถานการณ์ที่เรียบง่ายที่เป็นอย่างใดอย่างหนึ่งจะเป็นIEnumerable<T>หรือT- ใช้บันทึกการแทนGenericTypeArgumentsGetGenericArguments()

Type inputType = o.GetType();
Type genericType;
if ((inputType.Name.StartsWith("IEnumerable"))
    && ((genericType = inputType.GenericTypeArguments.FirstOrDefault()) != null)) {

    return genericType;
} else {
    return inputType;
}

1

นี่เป็นการปรับปรุงโซลูชันของ Eli Algranti ซึ่งจะใช้งานได้โดยที่IEnumerable<>ประเภทอยู่ในระดับใดก็ได้ในแผนผังการสืบทอด

Typeการแก้ปัญหานี้จะได้รับชนิดธาตุจากส่วนใด ถ้าชนิดไม่ได้เป็นIEnumerable<>ก็จะกลับชนิดผ่าน. GetTypeสำหรับวัตถุที่ใช้ สำหรับประเภทให้ใช้typeofจากนั้นเรียกวิธีการขยายนี้กับผลลัพธ์

public static Type GetGenericElementType(this Type type)
{
    // Short-circuit for Array types
    if (typeof(Array).IsAssignableFrom(type))
    {
        return type.GetElementType();
    }

    while (true)
    {
        // Type is IEnumerable<T>
        if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
        {
            return type.GetGenericArguments().First();
        }

        // Type implements/extends IEnumerable<T>
        Type elementType = (from subType in type.GetInterfaces()
            let retType = subType.GetGenericElementType()
            where retType != subType
            select retType).FirstOrDefault();

        if (elementType != null)
        {
            return elementType;
        }

        if (type.BaseType == null)
        {
            return type;
        }

        type = type.BaseType;
    }
}

1

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

/// <summary>Finds the type of the element of a type. Returns null if this type does not enumerate.</summary>
/// <param name="type">The type to check.</param>
/// <returns>The element type, if found; otherwise, <see langword="null"/>.</returns>
public static Type FindElementType(this Type type)
{
   if (type.IsArray)
      return type.GetElementType();

   // type is IEnumerable<T>;
   if (ImplIEnumT(type))
      return type.GetGenericArguments().First();

   // type implements/extends IEnumerable<T>;
   var enumType = type.GetInterfaces().Where(ImplIEnumT).Select(t => t.GetGenericArguments().First()).FirstOrDefault();
   if (enumType != null)
      return enumType;

   // type is IEnumerable
   if (IsIEnum(type) || type.GetInterfaces().Any(IsIEnum))
      return typeof(object);

   return null;

   bool IsIEnum(Type t) => t == typeof(System.Collections.IEnumerable);
   bool ImplIEnumT(Type t) => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>);
}

1
public static Type GetInnerGenericType(this Type type)
{
  // Attempt to get the inner generic type
  Type innerType = type.GetGenericArguments().FirstOrDefault();

  // Recursively call this function until no inner type is found
  return innerType is null ? type : innerType.GetInnerGenericType();
}

นี่คือฟังก์ชันแบบวนซ้ำซึ่งจะลงลึกในรายการประเภททั่วไปก่อนจนกว่าจะได้คำจำกัดความประเภทคอนกรีตโดยไม่มีประเภททั่วไปภายใน

ฉันทดสอบวิธีนี้กับประเภทนี้: ICollection<IEnumerable<ICollection<ICollection<IEnumerable<IList<ICollection<IEnumerable<IActionResult>>>>>>>>

ซึ่งควรกลับมา IActionResult



0

นี่คือวิธีที่ฉันมักจะทำ (ผ่านวิธีการขยาย):

public static Type GetIEnumerableUnderlyingType<T>(this T iEnumerable)
    {
        return typeof(T).GetTypeInfo().GetGenericArguments()[(typeof(T)).GetTypeInfo().GetGenericArguments().Length - 1];
    }

0

นี่คือเวอร์ชันนิพจน์แบบสอบถาม Linq ที่อ่านไม่ได้ของฉัน ..

public static Type GetEnumerableType(this Type t) {
    return !typeof(IEnumerable).IsAssignableFrom(t) ? null : (
    from it in (new[] { t }).Concat(t.GetInterfaces())
    where it.IsGenericType
    where typeof(IEnumerable<>)==it.GetGenericTypeDefinition()
    from x in it.GetGenericArguments() // x represents the unknown
    let b = it.IsConstructedGenericType // b stand for boolean
    select b ? x : x.BaseType).FirstOrDefault()??typeof(object);
}

โปรดทราบว่าเมธอดยังคำนึงถึงวิธีการที่ไม่ใช่แบบทั่วไปIEnumerableซึ่งจะส่งคืนobjectในกรณีนี้เนื่องจากใช้Typeอินสแตนซ์ที่เป็นรูปธรรมมากกว่าอาร์กิวเมนต์ อย่างไรก็ตามสำหรับx แสดงถึงสิ่งที่ไม่รู้จักฉันพบว่าวิดีโอนี้น่าสนใจแม้ว่ามันจะไม่เกี่ยวข้องก็ตาม ..

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