Description
Generally any implementation of IEnumerable<T> should be able to produce a T[] with a call to ToArray().
However, in some cases the IEnumerable<T> produced by a call to Cast() will in fact be an array of another type that can be converted on the fly by the implementation of GetEnumerator(). This works well in many cases and is a valuable optimisation (rather than going through CastIterator) but if ToArray() is called on it, the result is an ArrayTypeMismatchException that would not be expected by the user, especially since code might call ToArray() without knowing that Cast() had been used, or pass the result of Cast() to a method, not knowing that ToArray() would be used.
In particular this happens if you Cast() an array of one enum type to another enum type.
Activity
leppie commentedon Jul 3, 2015
A test case would be nice.
JonHanna commentedon Jul 3, 2015
leppie commentedon Jul 3, 2015
But even the simple case does not work in the presence of generics.
You would have to do:
Which means you have to cast to object then target.
Fix Enumerable.ToArray throwing in some cases.
JonHanna commentedon Jul 3, 2015
I don't follow, leppie. Generally
source.Cast<Enum1>()produces a perfectly fineIEnumerable<Enum1>generallysomeSequence.ToArray()wheresomeSequenceis anIEnumerable<Enum1>produces a perfectly findEnum1[]. It's surprising behaviour thatsource.Cast<Enum1>().ToArray()fails, especially since the code usingCastand the code usingToArraycould be quite far apart.JonHanna commentedon Jul 3, 2015
Ah, I get you now. As a rule
Enumerable<Cast>()does do pretty much the same thing asreturn (X)(object)y. (To be precise, it doesforeach (object obj in source) yield return (TResult)obj). The problem is with the optimisation of:Which can cause problems if
ToArray()then in turn tries to optimise forICollection<TElement> collection = source as ICollection<TElement>.But those two optimisations are hit by more code than is affected by this bug, so the key is to fix the bug without removing the optimisations.
leppie commentedon Jul 3, 2015
@hackcraft : In that case, the issue is legit :D Good catch.
It is pretty nasty that it does boxing though...
mikedn commentedon Jul 4, 2015
This really is a bug in the
Array.CopyToimplementation. On one hand the runtime allows you to freely cast enum arrays with the same underlying type and to unbox enums with the same underlying type but different actual type. On the other handArray.CopyToclaims that you can't copy to an enum array with the same underlying type. That doesn't make sense no matter how you look at it.stephentoub commentedon Jul 4, 2015
I agree with @mikedn, and I'd rather this be addressed at the source than at every possible place this pattern is employed. @jkotas, who should look at this?
jkotas commentedon Jul 4, 2015
This looks like enhancement request for
Array.Copythat is called byArray.CopyTo. The following does not work today:I do not see a fundamental problem with making this work. It should be a simple fix: change
GetSignatureCorElementTypetoGetVerifierCorElementTypethroughout the implementation ofArray.Copy. It should not be a breaking change because of it is changing throwing behavior to non-throwing behavior.However, such change should be reviewed by the FX design group for consistency issues. I believe that we generally disallow implicit enum conversions, e.g.
Array.SetValueor reflection APIs won't perform enum conversions. Is it a good idea to allow them forArray.Copyand keep everything else as is?stephentoub commentedon Jul 4, 2015
Either that request, or that in this code:
b would be false instead of true (it's true today), just as it's false today in this code:
But that would be a breaking change. It'd be better if the rest of Array could be made consistent with the current type checking behavior.
cc: @terrajobst
jkotas commentedon Jul 4, 2015
bshould really be true in this code:This looks like an invalid optimization performed by C# compiler. I have opened dotnet/roslyn#3830
mikedn commentedon Jul 5, 2015
Hmm, I'm tempted to say that this should be done only for
CopyToand only for thatCopyTothat's accessible via aICollection<T>. The Array's ownCopyandCopyToshould not be changed.Consider the following code:
I think that last
CopyToin the above example should work. As is now the runtime allows the cast toIList<Y>but the resulting interface is only partially functional.JonHanna commentedon Jul 5, 2015
I suspect this ultimately goes back to pre-generics .NET broken-but-intractable copying of pre-generics Java's broken-but-intractable array covariance.
To consider it another way. If this issue had been found in initial testing of
Castand it wasn't deemed too minor to care about, the conclusion could well have been that either the optimisation inCastwas too risky, or the optimisation inToArraywas too risky, and one or both of them should be removed.This would have stopped the problem hitting in this point that was introduced with .NET 3.5, but left any issues at 3.0 or earlier as is.But coming in now with that would cause a performance hit on code that benefits from the optimisation, which is likely quite a lot. As such a change like that in this PR has the advantage of being relatively low-impact, and it could be diked out if a deeper change made it unnecessary.
stephentoub commentedon Jul 5, 2015
While that may be true, the fix proposed in dotnet/corefx#2242 only fixes one particular case of this issue:
It doesn't fix using
ToList:nor using
List<T>'s ctor directly:nor without
Cast<TResult>:nor using some other
IList<T>.CopyTowrapper:nor any number of other occurrences of this pattern in the framework itself, in 3rd-party libraries, or in application code.
8 remaining items
VSadov commentedon Nov 18, 2016
It seems the right solution here is to make new or existing CopyTo to work with everything assignment-compatible.
I have entered https://github.com/dotnet/corefx/issues/13816 to track that.
VSadov commentedon Nov 18, 2016
I think we should close this for now until dotnet/corefx#13816 is fixed.
karelz commentedon Nov 18, 2016
Let's keep it open to not loose track.
karelz commentedon Nov 18, 2016
Blocked on dotnet/corefx#13816
Add test for Issue 2241 and close it.
Add test for Issue 2241 and close it.
Add test for Issue 2241 and close it. (#21979)