Why Cannot Ienumerable<Struct> Be Cast as Ienumerable<Object>

Why cannot IEnumerablestruct be cast as IEnumerableobject?

Why is the last line not allowed?

Because double is a value type and object is a reference type; covariance only works when both types are reference types.

Is this because double is a value type that doesn't derive from object, hence the covariance doesn't work?

No. Double does derive from object. All value types derive from object.

Now the question you should have asked:

Why does covariance not work to convert IEnumerable<double> to IEnumerable<object>?

Because who does the boxing? A conversion from double to object must box the double. Suppose you have a call to IEnumerator<object>.Current that is "really" a call to an implementation of IEnumerator<double>.Current. The caller expects an object to be returned. The callee returns a double. Where is the code that does the boxing instruction that turns the double returned by IEnumerator<double>.Current into a boxed double?

It is nowhere, that's where, and that's why this conversion is illegal. The call to Current is going to put an eight-byte double on the evaluation stack, and the consumer is going to expect a four-byte reference to a boxed double on the evaluation stack, and so the consumer is going to crash and die horribly with an misaligned stack and a reference to invalid memory.

If you want the code that boxes to execute then it has to be written at some point, and you're the person who gets to write it. The easiest way is to use the Cast<T> extension method:

IEnumerable<object> objects2 = doubleenumerable.Cast<object>();

Now you call a helper method that contains the boxing instruction that converts the double from an eight-byte double to a reference.

UPDATE: A commenter notes that I have begged the question -- that is, I have answered a question by presupposing the existence of a mechanism which solves a problem every bit as hard as a solution to the original question requires. How does the implementation of Cast<T> manage to solve the problem of knowing whether to box or not?

It works like this sketch. Note that the parameter types are not generic:

public static IEnumerable<T> Cast<T>(this IEnumerable sequence) 
{
if (sequence == null) throw ...
if (sequence is IEnumerable<T>)
return sequence as IEnumerable<T>;
return ReallyCast<T>(sequence);
}

private static IEnumerable<T> ReallyCast<T>(IEnumerable sequence)
{
foreach(object item in sequence)
yield return (T)item;
}

The responsibility for determining whether the cast from object to T is an unboxing conversion or a reference conversion is deferred to the runtime. The jitter knows whether T is a reference type or a value type. 99% of the time it will of course be a reference type.

Assign / cast more specific type of IEnumerableobject not working with structs

In C#, variance only works for reference types. From Covariance and Contravariance FAQ:

Variance is supported only if a type parameter is a reference type. Variance is not supported for value types.

And from the C# Spec, 13.1.3.2 Variance Conversion:

A type T<A1, …, An> is variance-convertible to a type T<B1, …, Bn> if T is either an interface or a delegate
type declared with the variant type parameters T<X1, …, Xn>, and for each variant type parameter Xi one of the
following holds:

  • Xi is covariant and an implicit reference or identity conversion exists from Ai to Bi
  • Xi is contravariant and an implicit reference or identity conversion exists from Bi to Ai
  • Xi is invariant and an identity conversion exists from Ai to Bi

Note that there is not implicit reference or identity conversion between any value type and object. There's only a boxing conversion.

Reason why

The thing is, T<A> can only be convertible to T<B> if the compiler can prove that T<A> can be accessed in the same exact way as T<B> without having to change the IL to make it work.

If either A or B are value types, then the two generic types are not convertible, since the compiler would possibly have to introduce boxing/unboxing instructions.

Why covariance and contravariance do not support value type

Basically, variance applies when the CLR can ensure that it doesn't need to make any representational change to the values. References all look the same - so you can use an IEnumerable<string> as an IEnumerable<object> without any change in representation; the native code itself doesn't need to know what you're doing with the values at all, so long as the infrastructure has guaranteed that it will definitely be valid.

For value types, that doesn't work - to treat an IEnumerable<int> as an IEnumerable<object>, the code using the sequence would have to know whether to perform a boxing conversion or not.

You might want to read Eric Lippert's blog post on representation and identity for more on this topic in general.

EDIT: Having reread Eric's blog post myself, it's at least as much about identity as representation, although the two are linked. In particular:

This is why covariant and contravariant conversions of interface and delegate types require that all varying type arguments be of reference types. To ensure that a variant reference conversion is always identity-preserving, all of the conversions involving type arguments must also be identity-preserving. The easiest way to ensure that all the non-trivial conversions on type arguments are identity-preserving is to restrict them to be reference conversions.

C# Cast object to IEnumerableT and use Linq extensions when the enumerable type is a value type e.g. enum

The array StringComparison[] implements the IEnumerable and IEnumerable<StringComparison> interfaces, but does not implement IEnumerable<object>.

Unfortunately, you can't use generic covariance to let you assign an object of type IEnumerable<StringComparison> to a variable of type IEnumerable<object>, as StringComparison isn't a reference type: you can't just access any old element of the StringComparison[] as if it was an object, because it doesn't have the normal object header etc.

What you can do is:

IEnumerable<object> items = ((IEnumerable)value).Cast<object>();

This works because arrays implement IEnumerable, and Enumerable.Cast is an extension method on IEnumerable, not IEnumerable<T>.

Every time you fetch an element from items, the Cast<object>() method gets involved and boxes the StringComparison value type into an object.

This means that iterating the list multiple times (as calling lots of linq methods will do) could be expensive, as it will allocate new objects each time. You might want to cache the boxed objects into a list to avoid this cost:

List<object> items = ((IEnumerable)value).Cast<object>().ToList();

Any workarounds for structs failing to cast to object when the struct is the covariant type of a generic object being being casted in C#?

According to Microsoft:

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.

Try doing a manual cast:

var castFails = Enumerable.Empty<Fails>().Cast<object>();

C# cannot convert class to IEnumerable

If you want to return the query results just write :

return queryJoin.ToList()

That executes the query and returns the results as a list. You need nothing more than :

public IEnumerable<StageTwo> Stage2Entries()
{
var queryJoin = (from inn in db.Input.Take(1)
join y in db.InputY on inn.YAction equals y.YAction
orderby inn.Id descending
select new StageTwo
{
Id = inn.Id,
Party = inn.XParty,
Currency = inn.Curr,
DrCr = y.DrAccount1,
Account = y.YAction,
Amount = inn.Amount
});
return queryJoin.ToList();
}

IEnumerable<T> is the interface implemented by all collections. StageTwo represents a a single object though so it doesn't make any sense for it to implement IEnumerable<T>

If you use EF/EF Core, you can execute the query asynchronously (ie without blocking) with ToListAsync() :

public async Task<IEnumerable<StageTwo>> Stage2Entries()
{
...
var list=await queryJoin.ToListAsync();
return list;
}

Casting an object to IEnumerableT where T is not known

Well, since you don't know the actual type of the items until runtime, you don't need to use the generic IEnumerable<T> interface; just use the non-generic one, IEnumerable (the generic one inherits from it):

private void WriteGenericCollection(object obj)
{
IEnumerable enumerable = (IEnumerable)obj;
foreach(object item in enumerable)
{
...
}
}


Related Topics



Leave a reply



Submit