Inheritance 对象表达式和错误FS0419:';基数';值只能用于直接调用重写成员的基本实现

Inheritance 对象表达式和错误FS0419:';基数';值只能用于直接调用重写成员的基本实现,inheritance,compiler-errors,f#,overriding,fsunit,Inheritance,Compiler Errors,F#,Overriding,Fsunit,标题中的错误是含糊不清的,但目前在某某上有两次点击,总共有五次(这表明它是一种罕见的野兽,所以不要期望在这里有太多的访问;)。我预计这个问题将排在第六位:p 表明错误文本具有误导性,如果从闭包中调用base,则会引发错误文本,但情况似乎并非总是如此,因为这样做有效: // in a closure, but no FS0419 let myObj = fun foo -> { new obj() with override this.ToString() = base.ToStri

标题中的错误是含糊不清的,但目前在某某上有两次点击,总共有五次(这表明它是一种罕见的野兽,所以不要期望在这里有太多的访问;)。我预计这个问题将排在第六位:p

表明错误文本具有误导性,如果从闭包中调用base,则会引发错误文本,但情况似乎并非总是如此,因为这样做有效:

// in a closure, but no FS0419
let myObj = fun foo ->
    { new obj() with override this.ToString() = base.ToString() + foo}
但这是失败的(我发现的最简单的例子):

我使用对象表达式代替继承的类型声明,并尝试通过构建自定义NUnit约束来创建新的FsUnit约束。下面是一个简化版本,显示了我所看到的问题:

let EndsWithIgnoreWhitespaceContraint expectedResult = 
    { new EndsWithConstraint(expectedResult) with 
        override __.ApplyTo<'T> (actual: 'T) =
            let actual = box actual
            if actual :? string then 
                actual :?> string
                |> fun s -> if isNull s then String.Empty else s
                |> fun s -> s.Trim()

                // error FS0419: 'base' values may only be used to make direct
                // calls to the base implementations of overridden members
                |> base.ApplyTo 
            else
                exn "String expected .. bla bla..." |> raise        }

// make it available to FsUnit's syntax style (overriding existing endWith)
let endWith = EndsWithIgnoreWhitespaceContraint

// using it:
"  hello world  " |> should endWith "world"

有人知道到底是什么原因导致了这个错误吗?

首先,您误解了“在闭包中不能使用base”的答案。它是指捕获基本身的闭包-是捕获阻止它工作,而不是闭包本身。在您的
{new obj}
示例中,
base
不会被任何闭包捕获。捕获整个对象,但
base
仅在
ToString
方法中直接使用

要进行说明,请尝试以下方法:

let myObj = 
    { new obj() with override this.ToString() = (fun() -> base.ToString())()}
let myObj = 
    { new obj() with 
      override this.ToString() = 
        let f = base.ToString  // Error here
        f()
    }
let myObj = 
    { new obj() with 
      override this.ToString() = 
        () |> base.ToString  // Same error
    }
此代码不会编译,因为闭包
fun()->base.ToString()
正在捕获
base

其次,使用对象方法作为函数并不像人们所期望的那样“直接”工作,因为.NET方法的表示方式不同于F#函数。相反,当遇到类似于
let x=obj.M
的问题时,编译器会将其视为
let x=fun a->obj.M(a)
——也就是说,将其包装在闭包中

要进行说明,请尝试以下方法:

let myObj = 
    { new obj() with override this.ToString() = (fun() -> base.ToString())()}
let myObj = 
    { new obj() with 
      override this.ToString() = 
        let f = base.ToString  // Error here
        f()
    }
let myObj = 
    { new obj() with 
      override this.ToString() = 
        () |> base.ToString  // Same error
    }
看到了吗?:-)

当您将管道导入对象方法时,编译器必须创建该闭包,然后将其传递给管道操作符。要进行说明,请尝试以下方法:

let myObj = 
    { new obj() with override this.ToString() = (fun() -> base.ToString())()}
let myObj = 
    { new obj() with 
      override this.ToString() = 
        let f = base.ToString  // Error here
        f()
    }
let myObj = 
    { new obj() with 
      override this.ToString() = 
        () |> base.ToString  // Same error
    }

有趣的是,我在问题中添加了您的最后一个示例,就在您发布此消息之前;)。我不知道调用对象的方法时通常会创建隐式闭包。但是当我编译
x |>f
时,它被编译成
fa
(iirc),所以按照你的故事,这个错误是由语义差异引起的(我没有注意到)。在
x |>obj.f
中,编译器不知道(或未优化以知道)是否
obj.f
将以闭包结束,如隐式
f'=obj.f;f'a
,或显式(借助内联)
obj.f a
。最终,一切都得到了优化,因此将管道引入方法不会带来性能开销。但是,错误检查发生在优化之前,因此不允许将管道引入到
base
方法中。因此,作为旁白,我们在这里得出结论,
a |>f
,即使是内联的,与
f a
(优化之前)几乎相同,但并不完全相同。当然,这是一堂有趣的课!所有这些边缘案例最终都来自于必须生活在.NET基础设施中。这就是为什么尽可能避免上课的原因。是的,当然没有。原则上,
base
值完全可以在闭包中捕获,只需捕获与正常值稍有不同的值,这就是它需要特别努力实现的原因。