4

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.

Matias Cicero
  • 25,439
  • 13
  • 82
  • 154
  • 1
    I don't understand what's wrong with Cast() appropriately for each case. – David Zech Aug 18 '15 at 19:58
  • 1
    How would you use these values if you don't know what type they are? – Lee Aug 18 '15 at 19:58
  • 1
    "My database's API returns them to me as a `List`" Seems like fixing the API would fix your issue. Any reason why it can't return different collection types? – D Stanley Aug 18 '15 at 19:59
  • @DStanley Database' API is out of my hands (I'm using AWS DynamoDB SDK). I can correctly convert `List` to `List` using `Cast()` and the application works fine, but I am looking forward to an alternative that doesn't stick me with just `string` and doesn't require me to build `if` cases for each primitive type – Matias Cicero Aug 18 '15 at 20:01
  • 1
    I cannot use any generic method, because I don't know `T` until runtime. This mapping method will be called by many models and sometimes I must cast to `IEnumerable` and sometimes to `IEnumerable` – Matias Cicero Aug 18 '15 at 20:04
  • 1
    Why would getting a typed `IEnumerable` help you if you don't know what type it holds until runtime? What are you actually trying to accomplish? – 31eee384 Aug 18 '15 at 20:05
  • @31eee384 My goal is to have a model containing an `IEnumerable` or `IEnumerable` property which would be correctly mapped from a `IEnumerable` that my database returns – Matias Cicero Aug 18 '15 at 20:07
  • @MatiCicero But you at some point have to know the compile-time type otherwise you won't be able to _use_ the model. – D Stanley Aug 18 '15 at 20:54

4 Answers4

7

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.

Community
  • 1
  • 1
Heinzi
  • 167,459
  • 57
  • 363
  • 519
  • "The result type of a casting operation must be known at compile time" - not true - you can cast to a generic type - that's how Linq's `Cast` works. – D Stanley Aug 18 '15 at 20:50
  • 1
    @DStanley: True, but generic types must be known at compile time as well: You can only call `Cast` if you provide it with a concrete type or another generic type. (And, in the latter case, the same argument can be made for *that* type. There has to be a concrete type somewhere at the end of the call chain.) You cannot call it with a runtime type. I'll clarify this in my question to avoid misunderstandings. – Heinzi Aug 18 '15 at 20:53
1

I believe System.Convert has what you need:

Type genericArg = collectionType.GetGenericArguments().First();
foreach(var obj in collection) {
    yield return Convert.ChangeType(obj, genericArg);
}
Ed T
  • 410
  • 4
  • 11
  • 1
    If you assume his DB layer handles getting the correct type, then yes. This solution allows him to specify the destination collection type and as long as conversion rules exist it will work. – Ed T Aug 18 '15 at 20:10
0

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.

Jeffrey Hantin
  • 35,734
  • 7
  • 75
  • 94
0

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>();
Deepak Bhatia
  • 1,090
  • 1
  • 8
  • 13