反射和泛型delegate引起的Windows Phone App奇怪异常

昨天因为一个bug拖到晚上1点才睡——还没解决。今天又逐行debug才发现问题所在,这个因为反射的引起的crash实在是太熬人了,记录一下或许能帮到其他人。

先说一下场景和上下文。之前Windows 8项目中需要用到一个解析数据到同一个基类派生出来类型的聚合类,其中有大量的解析函数,根据期望类型调用不同的parser。如果用if else写会很挫,于是我用了dictionary将type和function对应起来:

private Dictionary> _parseFunctions;

增加方法时候,由于懒得一个个typeof硬编码类型,于是用反射这样映射进去:

        protected void AddParseFunction(Func func) where T : BaseResult {
            _parseFunctions.Add(func.GetType().GetMethod("Invoke").ReturnType, func);
        }

除了GetMethod硬编码之外,用的时候十分轻松愉快。现在想在Windows Phone平台上实现相同的效果,结果上来就发现Func不好用:.net 4.5版本支持参数协变和返回值反变:

public delegate TResult Func(
	T arg
)

但3.5版本不支持。所以需要改写成delegate:

public delegate T ParserFunctionDelegate( string inputString ) where T : BaseResult;

代码虽然是WP7.1的,但在WP8上跑得顺畅——注意问题来了,这段代码可以在WP7.1上编译通过——之前因为想到反正要做两个平台兼容的App,所以一直用WP8模拟器/设备测试。昨晚在WP7平台上调试发现坏菜,启动后就各种异常出现,而且直接跑到UnhandledException,却没有任何详细异常信息和调用栈,最后出错地点只能看到是调用封装类的xaml.cs或者.cs里面。碰到的错误都是“Cannot load type xxx from assembly yyy”,具体异常出现过:

  • InvalidOperationException
  • MethodNotFoundException
  • TargetInvocationException
  • ……

根据具体调用地点出现了乱七八糟各种平时没用过的异常。但基本上一眼看去就可以确定是反射相关问题,可惜我没顺着相关调用代码一点点debug,而是傻逼呵呵地继续怀疑是UI层或者工程重组时候GAC缓存出错,这个上面浪费了大量时间,也是为什么到半夜才睡的原因。

那么问题在哪呢?首先不是泛型delegate本身的错,因为我直接用上面GetMethod代码去拿ReturnType没有引发异常。那么肯定是协变的问题,去掉了delegate的out关键字后……先是提示类型错误,因为dictionary里面存储的是基类类型delegate,但我们传进去的是where限定的子类类型delegate,去掉了协变支持后,返回值便没法存入dictionary中。由此导致AddParseFunction不能接受泛型delegate,添加进去的parser functions也必须返回BaseResult类型而非子类型了,由此函数只有增加一个type参数(这也是最开始要避免的)手动传进来,没法再用反射拿到类型。

这样修改完成后终于搞定了棘手的crash,但代码变得异常寒碜。要说代码必须写得烂一些还能忍,那么这个完全无法从crash调用栈上获取任何信息的问题就不能忍了。如果是因为协变参数出问题,你好歹定位到相应错误行上呢,要么干脆就编译不过,省得个大圈子修改错误。

Comments are closed.