Colin
Colin

Reputation: 22595

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?

Upvotes: 1

Views: 2249

Answers (3)

IV.
IV.

Reputation: 9168

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.

Upvotes: 1

Ostas
Ostas

Reputation: 949

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..
    }

Upvotes: 3

Ivan Khorin
Ivan Khorin

Reputation: 947

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.

Upvotes: 0

Related Questions