Reputation: 26331
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.
Upvotes: 4
Views: 8510
Reputation: 1100
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>();
Upvotes: 0
Reputation: 172468
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 anIEnumerable<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.
Upvotes: 7
Reputation: 36534
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.
Upvotes: 0
Reputation: 420
I believe System.Convert has what you need:
Type genericArg = collectionType.GetGenericArguments().First();
foreach(var obj in collection) {
yield return Convert.ChangeType(obj, genericArg);
}
Upvotes: 1