【问题标题】:Is there a generic way to populate various generic collections via reflection in C#?是否有一种通过 C# 中的反射填充各种通用集合的通用方法?
【发布时间】:2018-04-13 17:12:38
【问题描述】:

例如,这里有一些通过反射创建List<int> 的代码(是的,我知道有一些函数可以从数组转换为列表,问题不在于该解决方案,而是关于在不知道类型的情况下使用反射.

public static T Convert<T>(int[] src)
{
    Type genericClassType = typeof(T);
    Type[] typeParameters = genericClassType.GetGenericArguments();
    Type genericTypeDef = genericClassType.GetGenericTypeDefinition();
    Type constructedClass = genericTypeDef.MakeGenericType(typeParameters);
    T arrayLike = (T)Activator.CreateInstance(constructedClass);

    System.Reflection.MethodInfo method = arrayLike.GetType().GetMethod("Add", typeParameters);

    foreach (int value in src) {
        method.Invoke(arrayLike, new []{(object)value});
    }

    return arrayLike;
}

所以我可以这样称呼它

int[] src = {4, 5, 6};
List<int> copy = Convert<List<int>>(src);

但我也希望能够这样称呼它

int[] src = {4, 5, 6};
Stack<int> copy = Convert<Stack<int>>(src);

int[] src = {4, 5, 6};
Queue<int> copy = Convert<Queue<int>>(src);

但我不能,因为例如 Stack 没有 Add 方法

实际上,我正在做一些反序列化工作并试图使其成为通用或半通用的。源数据没有类型信息。它只是一个整数数组,但是当调用反序列化代码时,我知道我希望它是什么类型ListStackQueue 等等......所以,是否可以一般转换为给定类型或将我必须为每种类型的泛型编写自定义代码?

我确定在某种程度上ListStackQueue 是不同的,但我的 C/C++/Assembly 程序员看到给定一个 int 数组和所需类型,所有信息都可以重构给定只有一个通用的构造函数(已经出现在上面的代码中)。

【问题讨论】:

  • 请注意,StackQueueList 都有带有 IEnumerable&lt;T&gt; 的构造函数,您可以传递数组。
  • 你知道你在编译时反序列化成的类型(如 List)还是只在运行时?

标签: c# generics collections


【解决方案1】:

对于您的具体情况,如果您只是更改您的 Convert 方法以将 src 作为参数传递给构造函数,它将起作用:

public static T Convert<T>(int[] src)
{
    Type genericClassType = typeof(T);
    Type[] typeParameters = genericClassType.GetGenericArguments();
    Type genericTypeDef = genericClassType.GetGenericTypeDefinition();
    Type constructedClass = genericTypeDef.MakeGenericType(typeParameters);

    T arrayLike = (T)Activator.CreateInstance(constructedClass, src);

    return arrayLike;
}

所以你可以像这样使用它:

int[] src = { 4, 5, 6 };
Stack<int> copy = Convert<Stack<int>>(src);
Queue<int> copy2 = Convert<Queue<int>>(src);
List<int> copy3 = Convert<List<int>>(src);

因此,您可能希望在构造函数中添加一些类型检查,以验证它是否可以接受 IEnumerable&lt;int&gt; 的类型。

但是您必须注意顺序。例如,集合将按顺序包含:

copy:

6、5、4

copy2copy3

4、5、6

如果您希望copy 以相同的顺序包含它们,只需反转数组即可:

Stack<int> copy = Convert<Stack<int>>(src.Reverse().ToArray());


Try it out

【讨论】:

  • 很好的答案,但我想知道如何改写我的问题以获得更通用的答案。我实际上正在处理许多类型的集合和内容,尽管也许总是可以用一个 T 数组初始化一个泛型,而不管 T 是什么可能有效。不幸的是,知道你必须为堆栈反转。我会重新表述这个问题
  • 不要在有人回答后重新表述你的问题
  • @gman 我将添加另一个答案以尝试解决您提出的其他问题,但我会将其留在这里以解决以后的类似问题。
  • @gman 如果序列化代码从IEnumerable 构造函数接受它们的方式向后输出Stack 信息,则您只需为Stack 反转。
【解决方案2】:

那么,是否可以泛型转换为给定类型,还是我必须为每种泛型类型编写自定义代码?

如果他们都有一个接受 IEnumerable 的构造函数,那么试一试:

public static T Convert<T>(int[] src) where T : new()
{
    var obj = Activator.CreateInstance(typeof(T), src);
    return (T)obj;
}

Here it is as a Fiddle.

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main()
    {
        int[] src = {4, 5, 6};
        List<int> copy = Convert<List<int>>(src);
        Stack<int> copy1 = Convert<Stack<int>>(src);
        Queue<int> copy2 = Convert<Queue<int>>(src);

        Console.WriteLine(string.Join(",",copy));
        Console.WriteLine(string.Join(",",copy1));
        Console.WriteLine(string.Join(",",copy2));    
    }

    public static T Convert<T>(int[] src) where T : new()
    {
        var obj = Activator.CreateInstance(typeof(T), src);
        return (T)obj;
    }
}

【讨论】:

    【解决方案3】:

    实际上,我正在做一些反序列化工作并试图使其成为通用或半通用的。源数据没有类型信息。它只是一个整数数组,但是在调用反序列化代码时,我知道我希望它是 List、Stack、Queue 等什么类型……所以,是否可以一般地转换为给定的类型,或者我是否必须着手编写每种泛型的自定义代码?

    所以我的理解是,每种类型可能有不同的向集合添加元素的方法(并且可能会或可能不会将 IEnumerable&lt;T&gt; 作为构造函数参数),您需要在运行时指定它。我们可以为此使用Action

    public static T Convert<T, TArray>(IEnumerable<TArray> src, Action<T, TArray> addMethod) where T : new()
    {
        var myType = (T)Activator.CreateInstance(typeof(T));
    
        foreach (var element in src)
            addMethod(myType, element);
    
        return myType;
    }
    

    所以你需要指定add方法,你会这样调用代码:

    var copy = Convert<Stack<int>, int>(src, (lst, i) => lst.Push(i));
    

    在这种情况下,从堆栈中弹出的第一个元素将是6。这就是Stack&lt;T&gt; 工作方式的本质。堆栈是 LIFO(后进/先出)。因此,元素按照给定的顺序 (4,5,6) 推入堆栈,然后以相反的顺序 (6,5,4) 弹出。 Queue 将按 FIFO 顺序显示它们。

    这适用于奇数集合类型:

    public class SomeStrangeCollectionType<T>
    {
        private List<T> _myList = new List<T>();
        
        public void Gibberish(T value)
        {
            _myList.Add(value); 
        }
        
        public T[] ToArray()
        {
            return _myList.ToArray();   
        }
    }
    

    您可以致电Convert

    var strange = Convert<SomeStrangeCollectionType<int>, int>(src, (c, i) => c.Gibberish(i));
    

    并且会返回一个结果:

    Console.WriteLine(string.Join(",", strange.ToArray()));
    
    4,5,6

    Try it out

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-06-03
      • 2018-05-04
      • 1970-01-01
      • 2016-04-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多