VS 2012和VS 2015之间F#对歧义通用接口的处理差异导致后者出现编译错误



(注意:问题已更新为完整的可复制示例)

在将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#规范将是最终决定。

相关内容

  • 没有找到相关文章

最新更新