C# 动态泛型列表

7
我想创建一个泛型List<>,其类型在运行时声明。
我可以这样做,但由于它是动态的,我怀疑会有速度惩罚。我正在编写一个外部数据库的包装器,因此速度非常关键。
List<dynamic> gdb = new List<dynamic>()

我在动态泛型类型中阅读了this post,但无法使其工作。具体来说,该对象未显示为List,因此没有add方法。

    Type ac;

    switch (trail[dataPos].Type)
    {
        case GlobalsSubscriptTypes.Int32:
            ac = typeof(System.Int32);

            break;
        case GlobalsSubscriptTypes.Int64:
            ac = typeof(System.Int64);

            break;

        default:
            ac = typeof(System.String);

            break;
    }

    var genericListType = typeof(List<>);
    var specificListType = genericListType.MakeGenericType(ac);
    var gdb = Activator.CreateInstance(specificListType);

我该如何让gdb出现在以下选项中之一:

List<System.Int32>
List<System.Int64>
List<System.String>

1
在这种情况下,使用List<object>可能会更容易。如果类型在编译时未知,则泛型提供的编译时检查对您没有任何帮助。 - Servy
不是一个坏主意,但我希望列表被键入,因为它将成为查询的一部分。 - IamIC
@lukas 这已经在问题中提到了,就是他目前正在做的事情。 - Servy
2
@IanC 假设你有一个 List<dynamic> dList<object> o。然后继续赋值 int i = d[0];int j = (int)o[0]; 如果列表包含 int,那么 j 赋值肯定更快,如果列表包含隐式可转换为 int 的内容,则 'j' 赋值根本不起作用,而 'i' 则会起作用。 - jbtule
1
@jbtule 我昨天测试了一下,确实是对象胜出。 - IamIC
显示剩余4条评论
4个回答

5

当您使用Activator.CreateInstance时,可以将结果转换为适当的类型。例如,您可以使用IList

var gdb = (IList)Activator.CreateInstance(specificListType);
gdb.Add(1);

请注意,如果您添加的类型与泛型类型不匹配,则上述代码会抛出ArgumentException异常。

这就是我一直在寻找的答案。我之前在错误的地方进行了转换。 - IamIC
如果你只是使用非泛型接口,那么你最好直接使用 List<object>,这样就不需要使用反射来创建列表了。 - Servy
1
@Servy 需要将每个 int 和 long 进行装箱和拆箱,这会对性能造成重大影响。 - IamIC
2
@IanC 使用 IList 在这种方法中添加时仍会导致整数被装箱和拆箱... - Reed Copsey
1
@IanC 既然你使用的是非泛型方法,这种情况在这里肯定会发生。这里调用的add方法是public void Add(object item) - Servy
好的,有趣。你的想法可行。我现在这种做法唯一的优势似乎是列表已经被输入了,这意味着客户端代码不需要执行强制转换。 - IamIC

3

哦,gdb 是正确类型的。由于您在运行时确定类型,编译器并不知道它。因此,它被静态地定义为 object,并且不显示 Add 方法。您有几个选项来解决这个问题:

  1. Cast it to the correct type:

    switch (trail[dataPos].Type) 
    { 
        case GlobalsSubscriptTypes.Int32: 
            ((List<int>) gdb).Add(...); 
            break; 
        ...
        default: 
            ((List<String>) gdb).Add(...); 
            break; 
    } 
    
  2. Cast to a common supertype:

    ((System.Collections.IList) gdb).Add(...);
    
  3. Use dynamic invocation:

    dynamic gdb = Activator.CreateInstance(specificListType);
    gdb.Add(...);
    

1
在您的情况下,gdb 将始终是 System.Object,因为 CreateInstance 返回任何类型的对象。
您有几个选项 - 您可以将其强制转换为 IList(非泛型),因为您知道 List<T> 实现了此接口,并使用 IList.Add
或者,您可以只声明它为 dynamic,并使用动态绑定:
dynamic gdb = Activator.CreateInstance(specificListType);

这将允许您编写代码,就像它是一个适当类型的List<T>一样,并且调用将通过动态绑定在运行时绑定。

1

List<dynamic> 编译出的类型与 List<object> 完全相同,没有超过一个已经定义好类型的 List<int> 的速度惩罚,除了装箱值类型。但是,如果你要转换为 IList,你仍然会有装箱惩罚。

如果调用反射方法 Activator,可能会遇到麻烦,因为它比直接调用构造函数慢得多。

这些都有关系吗?除非你实际运行分析,否则你不会知道,因为它总是取决于你的实际使用情况。

我最好的猜测是你真正想做的是:

IList gdb;

switch (trail[dataPos].Type)
{
    case GlobalsSubscriptTypes.Int32:
        gdb = new List<int>();
        break;
    case GlobalsSubscriptTypes.Int64:
        gdb = new List<long>();
        break;
    default:
        gdb = new List<string>();
        break;
}

如果您真的需要在不装箱的情况下执行操作,请编写一个通用的辅助方法来完成所有工作:

switch (trail[dataPos].Type)
{
    case GlobalsSubscriptTypes.Int32:
        return Helper<int>(trail[dataPos]);
    case GlobalsSubscriptTypes.Int64:
        return Helper<long>(trail[dataPos]);
    default:
        return Helper<string>(trail[dataPos]);
}

感谢您清晰的解释。我喜欢 Helper 的想法(尽管我还没有考虑过它的代码)。由于多态性,我别无选择,只能在从数据库获取值的类中使用 dynamic。因此,我认为我应该简单地使用 List<dynamic>。 - IamIC
1
@IanC 我会选择能产生最简单代码的方式,然后再考虑速度问题。 - jbtule

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接