I just finished debugging a problem, where our program crashed on a production server, but never on development machines.
I have made this small program, which I could reproduce the issue with:
using System;
using System.Collections.Generic;
using System.Linq;
namespace RunTimeBug
{
class Program
{
static void Main(string[] args)
{
var coll = new Collection {{"Test", new Data()}, {"Test2", new Data()}};
var dataSequence = coll.Cast<Data>().ToList();
Console.WriteLine(dataSequence.Count开发者_运维问答);
}
}
class Collection : Dictionary<string,Data>, IEnumerable<Data>
{
public new IEnumerator<Data> GetEnumerator()
{
foreach(var v in Values)
yield return v;
}
}
class Data { }
}
When running on my machine, this code prints "2". When running on the production server, it fails with the following exception:
Unhandled Exception: System.InvalidCastException: Unable to cast object of type 'System.Collections.Generic.KeyValuePair
`2[System.String,RunTimeBug.Data]' to type 'RunTimeBug.Data'.
at System.Linq.Enumerable.<CastIterator>d__b0`1.MoveNext()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at RunTimeBug.Program.Main(String[] args)
The only difference I can find on these machines, is that the CLR Runtime is version 2.0.50727.4927 on machines, where it works, and version 2.0.50727.1433 on machines where it does not work.
As far as I can tell, the Cast extension method gets the wrong version of IEnumerable on the older machines, but the "right" one on newer machines.
Can anyone explain why I am seeing this ? What has changed between the 2 CLR Runtime versions, that might be causing this ?
Please note, I have already deployed a fix, and I am aware that the Collection class in the code above is poor design because it implements 2 different IEnumerable's. This was found "in the wild" in our product, so I would really like to know the exact cause.
I think the fix mentioned in the article that Jason linked to has something to do with your code working on later releases. I don't have access to the pre-SP1 version of Enumerable.Cast so it is a bit hard to guess.
One thing's for sure: once the code makes it into Enumerable.CastIterator() then it is guaranteed not to work in your case. That sets up an iterator to convert IEnumerable to IEnumerable<> and that iterator calls GetEnumerator() to initialize the iterator block. That can only call Dictionary.GetEnumerator(), not yours. Casting the KeyValuePair that this IEnumerable returns to Data will of course fail, as the exception tells you.
The current version of Enumerable.Cast() first tries to cast IEnumerable to IEnumerable<>. And that works. CastIterator() isn't used.
CLR version 2.0.50727.1433 is .NET 2.0 SP1 whereas CLR version 2.0.50727.4927 includes .NET 3.5 SP1 (for reference, see the Version History of the CLR).
The behavior of Enumerable.Cast
changed from .NET 3.5 to .NET 3.5 SP1 as outlined here.
Why do you think there is a "right" version to pick? Personally I'd be happy for it to throw am ambiguous compiler error at this point. Rather than Cast
, perhaps just a regular cast?
var dataSequence = ((IEnumerable<Data>)coll).ToList();
Cast<T>
casts the data, not the variable.
BTW, are you recompiling for each? I'm wondering if the issue is the compiler rather than the CLR or BCL.
精彩评论