模式匹配的类型测试不能识别List<’a>

我在Visual Studio论坛上读过一个很有趣的论坛帖子,内容大致是这样的:

当试图测试一个obj对象是否是一个空的list,我们不能用模式匹配的类型测试来达到目的,具体代码如下:

1 let empt (o: obj) =
2      match o with
3      | :? List<'a> as l when l.IsEmpty -> true
4      | _ -> false

编译器会报一个警告:warning FS0064: This construct causes code to be less generic than indicated by the type annotations. The type variable 'a has been constrained to be type 'obj'.

就是因为这个警告,“empt (List.tail [1]);;”会返回false因为它是List<int>,而不是List<obj>。更有趣的是如果你换而指定List<’a>为一个特定类型的List如List<int>,List<cahr>等,就能正常工作了。

你可能会很快想到使它成为强类型以摆脱这个问题,于是,有了下面的解决方案:

1 let emp (o : obj) =
2      match o with
3      | :? list<'a> as l when (l = []) -> true
4      | _ -> false

你可能还会想到拆箱obj类型的变量o,或者强制List<’a>为List<obj>。。。但是所有这些都将是一个美好的梦,仍然无法工作。

那么究竟如何解决它呢?为什么多态在这里不工作了?为什么类型推断算法统一了类型’a和obj?

答案就是:用反射而不是模式匹配的类型测试。一下代码能解决这个问题:

 1 open System.Reflection 
 2 let empt (o : obj) = 
 3     let genericListType = typedefof<List<_>>
 4     let bindingFlags = BindingFlags.GetProperty ||| BindingFlags.Instance |||      BindingFlags.Public
 5     match o.GetType() with
 6         |t when t.IsGenericType ->
 7             let genericType = t.GetGenericTypeDefinition()
 8             if genericType = genericListType then
 9                 t.InvokeMember("IsEmpty", bindingFlags, null, o, Array.empty) :?> bool
10             else false
11         |_ -> false

那么为什么要这样做呢?

问题就在于.NET运行时不会提供任何有效的方式来为一个未知的’a决定一个对象是否是一个List<’a>。唯一可行的机制就是像上述所用的反射,且自动转换成反射将会很不直观(例如:通常会需要创建一个泛型方法并用反射动态的调用这个方法,加上为类型测试实现反射的开销也很大)。因此,F# 要求用List<’a>来表示一个特定的类型’a;如果没有提供其它信息,类型推断将会默认它就是obj类型的。

posted @ 2012-05-17 10:22  tryfsharp  阅读(1567)  评论(2编辑  收藏  举报