简体   繁体   中英

How to cast an IEnumerable<object> to an IEnumerable<runtime type>

I'm trying to accomplish the following.

Suppose I have this data model:

public class Article
{
     public ICollection<string> Tags { get; set; }
}

These tags are retrieved from a database. My database's API returns them to me as a List<object> .

Therefore, I need to make a conversion from List<object> to something that implements ICollection<string> .

I am aware of the LINQ Cast<T>() method that cast its elements to the given type and returns the converted IEnumerable<T> .

However, I cannot use Cast<string>() because that would always cast my List<object> to IEnumerable<string> , not giving any options for models that have ICollection<double> properties (or any other type).

I can use reflection and get the generic type parameter:

Type genericArg = collectionType.GetGenericArguments().First();

But that would leave me with a runtime Type , which I cannot use as Cast<genericArg>() .

How can I cast an IEnumerable<object> to an IEnumerable of a dynamic Type ?.

I should note that no complex types are allowed on my model, so anything like:

public ICollection<Tag> Tags { get; set; }

will not happen. I only handle primitive types.

You have a basic misunderstanding about casting.

The result type of a casting operation must be known at compile time. ¹

Consider the following example:

string a = "abc";
object b = (object)a;
string c = (string)b;

The runtime type of a , b and c is the same. It's string . The compile-time type is different. Casting is only relevant for the compile-time type.

Thus, the answer to your question

How to cast an IEnumerable<object> to an IEnumerable<runtime type>

is: You don't. Casting does not make sense for runtime types.


That said, let me offer a solution to your real problem: Let's say you have an IEnumerable<object> values , a Type myTargetType and want to create a List<typeof(myTargetType)> containing the values.

First, you create the list using reflection:

var listType = typeof(List<>).MakeGenericType(myTargetType);
IList myList = (IList)Activator.CreateInstance(listType);

And then you fill the list:

foreach (var item in values)
{
    myList.Add(item);
}

Obviously, Add will throw an ArgumentException if an entry of values is not of runtime type myTargetType .


¹ The result type can be a generic type, but generic type parameters have to be specified at compile time as well .

I believe System.Convert has what you need:

Type genericArg = collectionType.GetGenericArguments().First();
foreach(var obj in collection) {
    yield return Convert.ChangeType(obj, genericArg);
}

Enumerable.Cast<T>(this IEnumerable source) is normally what you'd be looking for. It is possible to use reflection to close the generic type yourself if different variations are required:

class Program
{
    static void Main(string[] args)
    {
        var source = new List<object> {
            "foo",
            "bar",
            "baz"
        };

        var type = typeof(string); // or however you find out the type

        var castMethod = typeof(Enumerable)
            .GetMethod("Cast").MakeGenericMethod(
            new[] {
                type
            });

        var result = (IEnumerable<string>)
            castMethod.Invoke(null, new object[] {source});

        foreach (var str in result)
        {
            Console.WriteLine(str.ToUpper());
        }
    }
}

The other problem is that it is not meaningful to cast from one List<T> to another - the generic parameter is invariant , because the collection is read-write. (Arrays allow some such casting for historical reasons.) If you're only reading, though, the IEnumerable<T> returned from Cast is sufficient.

You need to implement a generic method which take result from your database api and return appropriate collection as per your model, something like below:

private ICollection<T> RetrieveTags()
{
      // Get tags using database api

      return tags.Cast<T>();
 }

Then call this method to get model as needed, for example:

ICollection<int>  t1 = RetrieveTags<int>();
ICollection<string>  t2 = RetrieveTags<string>();

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