简体   繁体   中英

Is there a generic way to populate various generic collections via reflection in C#?

For example here's some code to create a List<int> via reflection (yes, I know there are functions to convert from array to list, the question is not about that solution the question is about using reflection without knowing the types in advance.

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

So I can call it like this

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

But I'd also like to be able to call it like this

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

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

But I can't because for example Stack has no Add method

Effectively I'm doing some deserialization work and trying to make it generic or semi generic. The source data has no type info. It's just an array of ints but when calling the deserialization code I know what type I want it to be List , Stack , Queue etc... So, is it possible to generically convert to the given type or would I have to go about writing custom code for every type of generic?

I'm sure at some level List , Stack , Queue are different but C/C++/Assembly programmer in me sees that given an int array and the desired type all the info is there to reconstruct given only a generic constructor (already present in the code above).

For your specific case, if you just change your Convert method to pass the src as a parameter to the constructor, it will work:

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

So you can then use it like this:

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

So you may want to add some type checking in the constructor to verify that it can accept types of IEnumerable<int> .

But you have to be aware of the order. For example, the collections will contain, in order:

copy :

6, 5, 4

copy2 and copy3 :

4, 5, 6

If you want copy to contain them in the same order, just reverse the array:

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


Try it out

So, is it possible to generically convert to the given type or would I have to go about writing custom code for every type of generic?

If they all have a constructor that takes an IEnumerable, then give this a shot:

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

Effectively I'm doing some deserialization work and trying to make it generic or semi generic. The source data has no type info. It's just an array of ints but when calling the deserialization code I know what type I want it to be List, Stack, Queue etc... So, is it possible to generically convert to the given type or would I have to go about writing custom code for every type of generic?

So what I understand is that each type may have a different method of adding elements to the collection (and may or may not take an IEnumerable<T> as a constructor argument), and you need to specify that at runtime. We can use an Action for that:

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

So you need to specify the add method, and you would call the code like this:

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

In this case, the first element popped off the stack will be 6 . This is the nature of how a Stack<T> works. Stacks are LIFO (last-in/first-out). So the elements are pushed onto the stack in the order they are given (4,5,6) and then are popped off in the reverse order (6,5,4). A Queue would present them in FIFO order.

This works with odd collection types:

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

Which you can call Convert on:

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

And will return a result:

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

Try it out

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