【问题标题】:How to get concrete type in generic method如何在泛型方法中获取具体类型
【发布时间】:2020-05-25 21:06:07
【问题描述】:

我有一个方法返回一个实现接口的对象列表:

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

我想将结果转换为数据表:

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

我的 ConvertToDataTable 实现如下所示:

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

如何在泛型 ConvertToDataTable 方法中获取基础类型?

【问题讨论】:

  • 在你的情况下返回 ConvertToDataTable(result) 是不可能的吗?
  • @Ostas 这意味着以某种方式根据密钥在 GetData 方法中重复 switch 语句。我可以在调试器中看到具体类型。所以如果我能以某种方式使用反射来实现它会很酷 - 而不是重组我的代码
  • 我可能需要使用GetData(string key, out Type type)ConvertToDataTable (Type type),但这感觉不太对
  • 我明白了,没有意识到您在 GetData 中有一个 switch 语句。 GetData 可以是私有 List GetData(string key) where T: IFoo 并返回 (theTypeDeterminedAfterTheSwitch)result.
  • @Ostas - 我认为这不起作用,因为调用代码必须指定类型参数 - 所以它实际上是同一个问题

标签: c# reflection types


【解决方案1】:

将在编译时评估的 typeof 替换为在运行时评估的 .GetType,您将获得 coorect 类型,而不是接口:

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

【讨论】:

  • 不知道为什么会有反对票,我测试了它并且它有效。有一些反馈会很有趣。
  • 我希望能够在数据为空时返回包含列的 DataTable,但我认为这已经达到了最佳效果。这个对我有用。接受的答案并赞成
【解决方案2】:

GetType() 是什么让你在运行时获得具体的类。您接受的答案是您提出的问题的一个很好的解决方案。

现在,从您要完成的工作的角度来看,我想提出创建 DataTable 并不真正需要 RTTI。这是您的 ConvertToDataTable 方法的一个实现,它“不关心” T 是什么,只要它实现了 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;
    }

它获取 IFoo 接口中声明的任何内容:

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

它可以传入包含完全不同类的 IEnumerable,因为它们都实现了 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; }
}

控制台输出:

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

如果您想试用,可以在我们的 GitHub 上 download

【讨论】:

  • 附注我应该提一下:你说 FooA 和 FooB 对方法 void ConcreteImplementation(){} 有不同的实现。您可以通过简单地调用接口本身的方法来避免所有的大惊小怪。换句话说: someFoo.ConcreteImplementation();将调用正确的方法,前提是您在 interface IFoo { void ConcreteImplementation(); 中定义了它。 }(甚至有人会说这就是拥有接口的意义所在!)
  • 不回答问题
  • @Colin 请听我说,因为我只是想帮忙。如果问题是“我如何从 IEnumerable 填充 DataTable”,那么这里是一个完全充实的实现:github.com/IVSoftware/datatable-from-generic-interface。但是您的问题被描述为“如何在泛型方法中获取具体类型”,并且您对此进行了评论“//问题是 typeof(T) 始终是 IFoo - 而不是 FooBar” .我的答案显示使用 GetType() 可以在 anywhere (在通用方法内部或外部)获取具体类型。我坚持我的回答!只需运行我的仓库中的代码即可。
  • 我明白了。 GetType is 可能是该问题的最佳答案。让我困惑的是其他的东西。我不需要将单个项目转换为它们的具体类型——它们总是相同的具体类型。我已经编辑了我的问题以使其更清晰,如果您再次编辑您的问题,我将能够删除我的反对票
  • 很公平,谢谢,我完全理解!你在代码中所做的事情对我来说很有趣,我希望你不会介意我编辑的答案考虑了你所问的你想要达到的目标,这对我来说很有趣还挺有趣的。
【解决方案3】:
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; }}
}

我认为在这种情况下使用接口和泛型方法是个坏主意。使用继承可以使您的代码更简单、更简洁。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-04-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-06
    • 1970-01-01
    • 2019-12-22
    相关资源
    最近更新 更多