2

I want to implement a Cast method becuase I have tons of ugly source.Select(x => type(x)).ToArray(). So I write a simple extension:

public static IEnumerable<TResult> CastConvertible<TResult>(this IEnumerable<IConvertible> source)
{
    foreach (var value in source)
    {
        yield return (TResult) Convert.ChangeType(value, typeof (TResult));
    }  
} 

But it doesn't work because of error:

Error CS1929 'IEnumerable< int>' does not contain a definition for 'CastConvertible' and the best extension method overload 'ZEnumerable.CastConvertible< short>(IEnumerable< IConvertible>)' requires a receiver of type 'IEnumerable< IConvertible>'

But int is IConvertible while we know that IEnumerable<out T> is covariant so IEnumerable<DerivedType> could be converted to IEnumerable<BaseType>.

Here is an example:

int a = 10;
int[] b = {a};

IConvertible aa = a;
IEnumerable<IConvertible> bb = b;

So I should remove where constraint to be able to use this method but in this case I lose compile-time checking that type can be converted.

Why covariance doesn't work in this case?


I'm not using Enumerable.Cast<T> because it doesn't work for builtin types. For example short[] shorts = new int[] {1, 2, 3}.Cast<short>().ToArray(); will throw an exception, because Cast method uses internally non-generic IEnumerable so each value is boxed and then throws an exception, becuase an unboxing is only valid for exact initial type.

Alex Zhukovskiy
  • 9,565
  • 11
  • 75
  • 151
  • 1
    Why are you not using `IEnumerable.Cast`? – Yuval Itzchakov Feb 24 '16 at 08:47
  • Because it doesn't work in this case. Simple example `short[] shorts = new int[] {1,2,3}.Cast().ToArray()` – Alex Zhukovskiy Feb 24 '16 at 08:48
  • See [this](http://stackoverflow.com/questions/445471/puzzling-enumerable-cast-invalidcastexception) thread – Yuval Itzchakov Feb 24 '16 at 08:53
  • 2
    Jon Skeet has an excellent explanation of that behavior on his answer here http://stackoverflow.com/questions/12454794/why-covariance-and-contravariance-do-not-support-value-type – Victor Feb 24 '16 at 09:12
  • 1
    Instead of `CastConvertible(this IEnumerable source` use `this IEnumerable source` use `CastConvertible(this IEnumerable source`. Although this will work, eg. `var shorts = new[] { 1, 2, 3 }.CastConvertible().ToArray();`, the result of `ChangeType` still needs unboxing and incurs a performance penalty. You can add `where TSource :IConvertible` if you want to ensure only IConvertible types are used – Panagiotis Kanavos Feb 24 '16 at 09:15
  • @PanagiotisKanavos Peformance is not an issue here, I just wanted to clear API for a function, and in this case we require to specify both source and target types. I wanted to avoid it. But it seems that it's another language limitation and nothing to do here. – Alex Zhukovskiy Feb 24 '16 at 09:37
  • @Victor thanks, I googled this behaviour but didn't find this very article which is what I required. As i said there is nothing to do, no elegant solution exist. The only way to do a fast-and-elegant solution it's just use T4 templates and generate an overload for each primitive type. – Alex Zhukovskiy Feb 24 '16 at 09:39

1 Answers1

3

Excerpt from Covariance and Contravariance in Generics:

Variance applies only to reference types; if you specify a value type for a variant type parameter, that type parameter is invariant for the resulting constructed type.

So the key point in your question is not the builtin, but value type.

One way to resolve the issue is to add another generic argument to your extension method:

public static IEnumerable<T, TResult> CastConverible<TResult>(this IEnumerable<T> source)
    where T : IConvertible

But it will not be so useful because the caller will need to specify both generic types, not only the TResult.

Another way is to define your extension method on non generic IEnumerable (similar to Cast)

public static IEnumerable<TResult> CastConverible<TResult>(this IEnumerable source)

But this way you cannot constrain it to IConvertible elements.

The best option I see is to replace you method with two new extension methods:

public static IEnumerable<IConvertible> AsConvertible<T>(this IEnumerable<T> source)
    where T : IConvertible
{
    return source as IEnumerable<IConvertible> ?? source.Select(item => (IConvertible)item);
}

public static IEnumerable<TResult> ConvertTo<TResult>(this IEnumerable<IConvertible> source)
{
    return source as IEnumerable<TResult> ?? 
        source.Select(item => (TResult)Convert.ChangeType(item, typeof(TResult)));
}

The sample usage will not be so concise, but still fluent:

int[] a = { 1, 2, 3 };
var b = a.AsConvertible().ConvertTo<byte>();
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • Why not just `a.Cast().ConvertTo()`? – drowa Aug 04 '16 at 22:20
  • @drowa OP wanted the method to appear only to enumerables of types that implement `IConvertible` (note the `where T : IConvertible` constraint). While your suggestion allows to be called on any enumerable and get a runtime exception if called on wrong type. i.e. the difference is compile vs run time verification. – Ivan Stoev Aug 04 '16 at 23:06