Generics F#在VS 2012和VS 2015之间处理不明确的通用接口之间的差异导致后者出现编译错误
(注:问题更新为完整的可复制示例) 将F#项目从VS 2012迁移到VS 2015后,我收到一个关于接口某些用法的错误。特别是当一个类型实现两个泛型接口时。我知道这是不允许直接在F#中使用的,但这种类型来自C# 要重现问题,请执行以下操作: 1.C#中的类型定义: 将其粘贴到某个类库中Generics F#在VS 2012和VS 2015之间处理不明确的通用接口之间的差异导致后者出现编译错误,generics,visual-studio-2012,interface,f#,visual-studio-2015,Generics,Visual Studio 2012,Interface,F#,Visual Studio 2015,(注:问题更新为完整的可复制示例) 将F#项目从VS 2012迁移到VS 2015后,我收到一个关于接口某些用法的错误。特别是当一个类型实现两个泛型接口时。我知道这是不允许直接在F#中使用的,但这种类型来自C# 要重现问题,请执行以下操作: 1.C#中的类型定义: 将其粘贴到某个类库中 公共接口基{} 公共接口IPrime:Base { T值{get;} } 公共接口IFloat:IPrime { } 公共接口IInt:IFloat、IPrime { int Salary{get;} } 公共抽
公共接口基{}
公共接口IPrime:Base
{
T值{get;}
}
公共接口IFloat:IPrime
{
}
公共接口IInt:IFloat、IPrime
{
int Salary{get;}
}
公共抽象类素数:IPrime
{
公共T值{get;受保护的内部集;}
公共静态隐式运算符T(素数值)
{
返回值;
}
}
公共类FFloat:Prime,IFloat
{
公共资源(双倍值)
{
这个。值=值;
}
公共双薪{get;set;}
}
公共类FInt:Prime,IInt
{
公共FInt(int值)
{
这个。值=值;
}
公共整数{get;set;}
int-ipinite.Value{get{返回此.Value;}}
double IPrime.Value{get{返回此.Value;}}
}
2.F#中类型的用法:
在Visual Studio 2012中的使用,工作:
open SomeClassLib
[<EntryPoint>]
let main argv =
let i = new FInt(10)
let f = new FFloat(12.0)
let g = fun (itm: SomeClassLib.Base) ->
match itm with
| :? IInt as i -> i.Value
| :? IFloat as i -> i.Value |> int
| _ -> failwith "error"
Visual Studio 2015
| :? IInt as i -> i.Value // Value is float
| :? IFloat as i -> i.Value |> int // Value is float
我想知道这种差异是从哪里来的
问题/备注
我觉得奇怪的是,编译器似乎“选择”了一个赋值有效,而另一个不起作用,这会导致不经意的错误,有时甚至很难出错。坦率地说,我有点担心,在类型推断可以在int和float之间选择的地方,它现在会倾向于float,在过去是int
2012年和2015年之间的区别似乎在于,前者在晋升时在遭遇中占据第一位,而后者似乎占据最后一位,但我无法明确证实这一点
这是一个bug还是对现有特性的改进?恐怕我需要做一些重新设计来消除歧义,除非有人知道一个简单的方法来处理这个问题(它只发生在大约50个地方,可以手工修复,但不是很好)
初步结论
我很清楚,原始类型可能被认为是不明确的,可能是糟糕的设计,但是.NET语言和MSIL都支持它
我知道F#不支持在同一方法或属性上混合泛型类型,这是一种我可以接受的语言选择,但它的类型推断会做出无法预测的决定,我认为这是一个bug
无论如何,我认为这应该是一个错误,类似于在F#中处理重载成员时得到的错误,在这种情况下,错误非常清楚,并列出了选项。我将C#代码中的接口顺序替换为这段代码和下面在VS 2013中编译的代码
公共接口IInt:IPrime,IFloat
{
int Salary{get;}
}
let g=fun(itm:Base)->
将itm与
| :? 按i->i.值输入
| :? IFloat as i->i.值|>int
|->failwith“error”
根据接口的顺序,我怀疑一个“Value”成员被另一个隐藏(被FSharp的透视图遮蔽)
我将您的模式匹配编码为这样,并验证了在本例中,接口顺序并不重要
let g = fun (itm: Base) ->
match itm with
| :? IPrime<int> as i -> i.Value
| :? IPrime<float> as i -> i.Value |> int
| _ -> failwith "error"
let g=fun(itm:Base)->
将itm与
| :? i优先于i->i.值
| :? i参数为i->i.值|>int
|->failwith“error”
在我看来,这似乎是实施细节上的一个变化。我不确定我是否会称之为bug。如果这是一个bug,那么F#规范将是最终的决定 我认为你应该消除歧义,不管是否有“好”的解决方案。还有,这在C#中是如何工作的?既然C#在
int
和double
之间进行了隐式类型转换,那么您不担心C#中会发生这种情况吗?F#中的赋值是“因为两个接口都是由C#type实现的,所以它们不能有两个名为add
的属性。一个(或两个)必须是显式接口实现(例如float ITest.Add{get;set;}
)。你能把iWithtware
的C#实现发布出来吗?@Daveshaw,让我检查一下(旧库,我们通常使用编译版本)。是的,我认为你是对的\@费奥多,我完全同意,但有时我们受到预算的限制,无法重新设计也用于其他项目或客户的冻结库……有趣的是,当直接粘贴到interactive:error FS0443中时,代码在i上给出了一个相当明确的错误,这两个定义都是定义:此类型在不同的位置实现相同的接口泛型实例化ITest
和ITest
。此版本的F#中不允许这样做。你应该看一看,不确定它是否符合复制品的条件。谢谢。我很惊讶地看到接口的顺序很重要,这是一个很大的优势,非常可疑。这是一个快速解决方案。我只是试了一下,没有100秒的演员失误,结果是零,太棒了!(虽然我仍然认为这是F#的一个缺陷)(ps,为我的上一条评论道歉,我说得太匆忙了)。嗯,需要补充的重要细节:在交换逗号分隔的接口列表顺序后,它在VS2015中编译得很好,但在VS2012中不再如此。幸运的是,我们不打算同时维护这两个版本。这也可能指出原因在于C#4和C#5编译器,不确定。
let g = fun (itm: Base) ->
match itm with
| :? IPrime<int> as i -> i.Value
| :? IPrime<float> as i -> i.Value |> int
| _ -> failwith "error"