简体   繁体   中英

How to get concrete type in generic method

I have a method that returns a List of objects that implement an interface:

private List<IFoo> GetData(string key)
{    
   ...returns a different concrete implementation depending on the key
    switch (key)
    {
        case "Bar":
            return new List<Bar>();//Bar:IFoo
            break;

        case "Foo":
            return new List<Foo>();//Foo:IFoo
            break;


        case "FooBar":
            return new List<FooBar>();//FooBar:IFoo
            break;
        //etc etc - (quite a lot of these)
    }
}

And I want to convert the result to a DataTable:

var result = GetData("foobar");
return ConvertToDataTable(result)

and my implementation of ConvertToDataTable looks something like this:

private DataTable ConvertToDataTable<T>(IEnumerable<T> data)
{
    //problem is typeof(T) is always IFoo - not FooBar
    PropertyInfo[] properties = typeof(T).GetProperties();

    DataTable table = new DataTable();
    foreach (var prop in properties)
    {
        table.Columns.Add(prop.DisplayName, prop.PropertyType);
    }
    //etc..
}

How can I get the underlying type in the generic ConvertToDataTable method?

GetType() is what gets you the concrete class at runtime. The answer you accepted is a good solution for the question you asked.

Now, from the point of view of what you're trying to accomplish, I wanted to offer that creating your DataTable doesn't really require that RTTI. Here's an implementation of your ConvertToDataTable method that "doesn't care" what T is, as long as it implements IFoo.

    private static DataTable ConvertToDataTable<T>(IEnumerable<T> data)
    {
        // Reflect the properties from T which is IFoo
        PropertyInfo[] properties = typeof(T).GetProperties();

        DataTable table = new DataTable();
        // Add columns 
        foreach (var prop in properties)
        {
            table.Columns.Add(
                prop.Name, 
                prop.PropertyType
            ).DataType = prop.PropertyType;
        }
        Console.WriteLine("Inside the generic method: ");
        // Add rows 
        foreach (var item in data)
        {
            // RE: For "the question you asked": Use GetType() for object info.
            Console.WriteLine("...the concrete Type is " + item.GetType().Name); 
            // I would ask, though, do you really need it for anything here?

            // But for "the thing you're trying to accomplish" (making a DataTable)
            // - This goes by the public properties declared in the interface IFoo.
            // - It pulls properties GENERICALLY for ANY class that implements IFoo.
            object[] values = 
                properties.Select(property => property.GetValue(item)).ToArray();                
            table.Rows.Add(values);
        }
        return table;
    }

It picks up whatever is declared in the IFoo interface:

internal interface IFoo
{
    int ID { get; }
    string Name { get; }
    string Text { get; set; }
}

It works to pass in IEnumerable containing completely different classes because they both implement IFoo:

class FooA : IFoo
{
    public int ID { get; } = 1;
    public string Name { get; } = "I am Foo A";
    public string Text { get; set; }
}
class FooB : IFoo
{
    public int ID { get; } = 2;
    public string Name { get; } = "I am Foo B";
    public string Text { get; set; }
}

Console Output:

Inside the generic method:
...the concrete Type is FooA
...the concrete Type is FooB
D I S P L A Y    P O P U L A T E D    T A B L E
ID      Name    Text
1       I am Foo A
2       I am Foo B

You can download from our GitHub if you want to try it out.

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

namespace StackOverflow001
{
    class Program
    {
        static void Main(string[] args)
        {
            var data = GetData("Foo");
            var table = ConvertToDataTable(data);

            data = GetData("Bar");
            table = ConvertToDataTable(data);

            data = GetData("FooBar");
            table = ConvertToDataTable(data);
        }

        static IEnumerable<FooBase> GetData(string key) =>
            key switch
            {
                "Foo" => new List<Foo>(),
                "Bar" => new List<Bar>(),
                "FooBar" => new List<FooBar>(),
                _ => throw new ArgumentException(nameof(key)),
            };

        static DataTable ConvertToDataTable(IEnumerable<FooBase> data)
        {
            var properties = data switch
            {
                List<Foo> _ => typeof(Foo).GetProperties(),
                List<Bar> _ => typeof(Bar).GetProperties(),
                List<FooBar> _ => typeof(FooBar).GetProperties(),
                _ => throw new ArgumentException(nameof(data)),
            };

            DataTable table = new DataTable();
            foreach (var prop in properties)
            {
                table.Columns.Add(prop.Name, prop.PropertyType);
            }

            return table;
        }
    }

    interface IFoo {}
    abstract class FooBase : IFoo { }
    class Foo : FooBase { public int FooProp { get; set; } }
    class Bar : FooBase { public int BarProp { get; set; } }
    class FooBar : FooBase { public int FooBarProp { get; set; }}
}

I think that using interface and generic methods is a bad idea in this situation. Using inheritance can make your code much easier and cleaner.

Replace typeof which is evaluated at compileTime by.GetType which is evaluated at runtime and you will get the coorect type, not the interface:

private DataTable ConvertToDataTable<T>(IEnumerable<T> data)
    {
        Type dataType;
        if (data != null && data.Count() != 0)
        {
            //problem is typeof(T) is always IFoo - not FooBar
            //typeof(T) will always return IFoo

            //Will return the correct type
            dataType = data.First().GetType();
        }
        else
        {
            return new DataTable();
            //or throw ?
        }

        PropertyInfo[] properties = dataType.GetProperties();

        DataTable table = new DataTable();
        foreach (var prop in properties)
        {
            table.Columns.Add(prop.DisplayName, prop.PropertyType);
        }
        //etc..
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM