我刚刚完成调试问题,我们的程序在生产服务器上崩溃,但从未在开发机器上崩溃。
我制作了这个小程序,我可以通过以下方式重现这个问题:
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 { }
}
在我的机器上运行时,此代码打印“2”。在生产服务器上运行时,它失败并出现以下异常:
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)
我可以在这些机器上找到的唯一区别是,CLR Runtime在机器上的版本为2.0.50727.4927,在它可以工作的机器上,以及在它不起作用的机器上的版本2.0.50727.1433。
据我所知,Cast扩展方法在旧机器上获取错误的IEnumerable版本,但在新机器上获得“正确”版本。
任何人都可以解释为什么我会看到这个吗? 2个CLR运行时版本之间发生了哪些变化,可能导致此问题?
请注意,我已经部署了一个修复程序,并且我知道上面代码中的Collection类设计很差,因为它实现了2个不同的IEnumerable。这是在我们的产品中“在野外”发现的,所以我真的想知道确切的原因。
答案 0 :(得分:3)
我认为Jason链接到的文章中提到的修复与您在以后的版本中工作的代码有关。我无法访问Enumerable.Cast的SP1之前的版本,因此有点难以猜测。
有一件事是肯定的:一旦代码进入Enumerable.CastIterator(),那么保证不会在你的情况下工作。这设置了一个迭代器,将IEnumerable转换为IEnumerable&lt;&gt;并且迭代器调用GetEnumerator()来初始化迭代器块。那只能调用Dictionary.GetEnumerator(),而不是你的。将此IEnumerable返回给Data的KeyValuePair转换为当然会失败,正如异常所示。
当前版本的Enumerable.Cast()首先尝试将IEnumerable转换为IEnumerable&lt;&gt;。这很有效。不使用CastIterator()。
答案 1 :(得分:2)
为什么你认为有一个“正确”的版本可供选择?就个人而言,我很高兴在这一点上抛出模糊的编译器错误。而不是Cast
,也许只是常规演员?
var dataSequence = ((IEnumerable<Data>)coll).ToList();
Cast<T>
投射数据,而不是变量。
答案 2 :(得分:2)
CLR版本2.0.50727.1433是.NET 2.0 SP1,而CLR版本2.0.50727.4927包括.NET 3.5 SP1(供参考,请参阅Version History of the CLR)。
如Enumerable.Cast
所述,here的行为已从.NET 3.5更改为.NET 3.5 SP1。