(注意:问题已更新为完整的可复制示例)
在将F#项目从VS 2012迁移到VS 2015之后,我收到了一个关于接口某些用法的错误。特别是当一个类型实现两个通用接口时,就会发生这种情况。我知道F#中不允许直接使用这种类型,但这种类型来自C#。
重现问题:
1.C#中的类型定义:
将其粘贴到某个类库中。
public interface Base { }
public interface IPrime<T> : Base
{
T Value { get; }
}
public interface IFloat : IPrime<double>
{
}
public interface IInt : IFloat, IPrime<int>
{
int Salary { get; }
}
public abstract class Prime<T> : IPrime<T>
{
public T Value { get; protected internal set; }
public static implicit operator T(Prime<T> value)
{
return value.Value;
}
}
public class FFloat : Prime<double>, IFloat
{
public FFloat(double value)
{
this.Value = value;
}
public double Salary { get; set; }
}
public class FInt : Prime<int>, IInt
{
public FInt(int value)
{
this.Value = value;
}
public int Salary { get; set; }
int IPrime<int>.Value { get { return this.Value; } }
double IPrime<double>.Value { get { return this.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中打开相同的解决方案,你会得到错误
错误FS0001:类型不匹配。应为
float -> float
,但给定的是float -> int
。类型'float'
与类型'int'
不匹配
这当然很容易通过类型转换来纠正,但令人惊讶的是,它不会再在Visual Studio 2012中加载了(好吧,有很多方法可以让它在这两种情况下都工作,在这个例子中它很简单)。
3.调查结果
如果你将鼠标悬停在i.Value
上,你会得到:
Visual Studio 2012
| :? IInt as i -> i.Value // Value is int
| :? IFloat as i -> i.Value |> int // Value is float
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#不支持在同一方法或属性上混合泛型类型,这是我可以接受的语言选择,但它的类型推断会做出无法预测的决定,我认为这是一个错误。
无论哪种方式,我认为这都应该是一个错误,类似于您在F#中处理重载成员时遇到的错误,在这种情况下,错误非常明显,并列出了选项。
我将C#代码中接口的顺序交换为该代码和以下代码,这些代码按照VS 2013 编译
public interface IInt : IPrime<int>, IFloat
{
int Salary { get; }
}
let g = fun (itm: Base) ->
match itm with
| :? IInt as i -> i.Value
| :? IFloat as i -> i.Value |> 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"
对我来说,这似乎是实现细节的改变。我不确定我是否会称之为bug。如果这是一个bug,F#规范将是最终决定。