Matias Cicero
Matias Cicero

Reputation: 26281

How can I cast IEnumerable<?> to IEnumerable<string>?

This question was completely edited. To see the original one, see the edit history


I think I should explain a little bit of my situation and context of the problem.

I'm inspecting a type for a property annotated with a custom attribute. I then get the value of that property, but as an object:

IEnumerable<object> propertiesValues = myType.GetProperties().Where(p => p.GetCustomAttributes(typeof(MyCustomAttribute)) != null);

Once I have these values I want to call my method Map on them, which converts them to a different object depending on their type. This is because my database API requires so.

foreach(object value in propertiesValues)
{
     object mapped = Map(value);
     // ...
}

I need all IEnumerable<NumberType> values, to be converted to IEnumerable<string>. So my first approach was:

public static object Map(object value)
{
    if(value is IEnumerable<short> || value is IEnumerable<int> || ....)
         return ((IEnumerable<object>) value).Cast<string>();
}

However, this is throwing a runtime exception, as I cannot cast IEnumerable<int> to IEnumerable<object> for example.

Upvotes: 3

Views: 10758

Answers (2)

Eric Lippert
Eric Lippert

Reputation: 660159

The feature you want is called covariance, and C# supports it on IEnumerable<T> only when the type arguments are both reference types. That is, IEnumerable<string> may be converted to IEnumerable<object>, but IEnumerable<int> may not be so converted.

The reason is: when you get a sequence of strings, those string references are already legal object references. But an int only becomes a legal object reference when it is boxed; where does the compiler know to insert the boxing operation? It doesn't, so the conversion is illegal.

To convert an IEnumerable<int> to IEnumerable<object> you have to do the boxing explicitly via a projection:

from number in items select (object)item

or

items.Select(item => (object) item)

That will be IEnumerable<object>, and you can then do what you like to that.

Or use the Cast sequence operator

items.Cast<object>()

which does the same thing. Or use its query form:

from object item in items select item

Or simply use the non-generic IEnumerable, which gives you a sequence of boxed values if in fact it is a sequence of structs.

I note though that your plan of then casting IEnumerable<object> to IEnumerable<string> via the Cast extension method is doomed to failure. Those items will be boxed ints. Boxed ints cannot be unboxed to string.

It seems like you are fighting the type system here instead of working with it. Perhaps you should describe your real problem, rather than your attempts to do an end-run around the type system to solve it.

Upvotes: 17

Daniel A. White
Daniel A. White

Reputation: 190942

Why don't you do this

var source = ... /// IEnumerable<???>
var destination = source.Select(item => item.ToString());

You could update Map's signature as well:

object Map<TItem, TSource>(TSource source) 
  : where TSource : IEnumerable<TItem>

or

object Map<TItem>(IEnumerable<TItem> source) 

Upvotes: 2

Related Questions