F#代码调用包含Func参数的ac#方法的行为异常

bst*_*ack 6 c# f# c#-to-f#

最近,我们遇到了一个F#代码调用C#代码的问题。我已经尽可能简化了这个问题。C#代码如下:

using System;

namespace CSharpLib
{
    public abstract class InputMessageParent
    {
        public readonly Guid Id;

        protected InputMessageParent(Guid id) { this.Id = id; }
    }

    public class Result { public string ResultString; }

    public enum FunctionResult
    {
        None,
        Good,
        Bad
    }

    public class ConfigurationBuilder
    {
        public Result DoWork<TMessage>(
            string param1,
            Func<TMessage, FunctionResult> action)
            where TMessage : InputMessageParent
        {
            return new Result() { ResultString = "Good" };
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

F#代码需要调用ConfigurationBuilders DoWork函数。请注意,DoWork函数采用两个参数,一个简单的字符串和一个Func作为第二个参数。Func接受必须从InputMessageParent继承的TMessage,并返回简单的Result类型。F#代码如下:

open System

type InputMessageConcreteTypeA =
    inherit CSharpLib.InputMessageParent
    val Property1:string
    new (id, property1Value) =
        {
            inherit CSharpLib.InputMessageParent(id)
            Property1 = property1Value
        }

[<EntryPoint>]
let main argv =

    let actionImpl (input:InputMessageConcreteTypeA) =
    CSharpLib.FunctionResult.Good

    let builder = new CSharpLib.ConfigurationBuilder()
    builder.DoWork("param1", actionImpl) |> ignore

    0
Run Code Online (Sandbox Code Playgroud)

此代码无法编译。actionImpl类型为InputMessageConcreteTypeA-> CSharpLib.FunctionResult,这正是DoWork期望的第二个参数的类型,但它给了我以下错误:This expression was expected to have type 'Func<'a,CSharpLib.FunctionResult>' but here has type 'b -> CSharpLib.FunctionResult'

有趣的是,如果我将代码更改为以下代码,则可以编译:

[<EntryPoint>]
let main argv =

let actionImpl (input:InputMessageConcreteTypeA) =
    CSharpLib.FunctionResult.Good

let builder = new CSharpLib.ConfigurationBuilder()
builder.DoWork("param1", fun input -> actionImpl(input)) |> ignore

0
Run Code Online (Sandbox Code Playgroud)

为什么代码会为内联匿名函数编译,该匿名匿名函数与actionImpl具有完全相同的类型定义,但是如果我直接传递actionImpl就不会编译?内联匿名函数看起来毫无意义,但这是我们目前唯一的解决方案。有没有更好的办法?

Fyo*_*kin 5

的F#函数类型'a -> 'b,事实上,同为C#类型Func<a,b>

它们之所以不同的原因有点儿争议,但结果是您不能随心所欲地将一种类型传递给另一种。类型不匹配。这就是编译器告诉您的内容:预期类型为Func <...>,但是这里的类型为'b-> ...

但是,对于lambda表达式,编译器会例外。通常,lambda表达式fun x -> e将具有类型'a -> 'b(where x:'ae:'b),但是如果从上下文中已经知道预期类型是Func<_,_>编译器,则编译器会将lambda表达式编译为a Func

设置此异常是为了简化与大量使用lambda表达式的.NET库(例如LINQ)的互操作。但是,此异常不适用于通过名称来引用命名函数。它可能应该适用,但不是。

如果您不希望有无意义的lambda表达式,唯一的另一种方法是Func<_,_>通过调用类型的构造函数并将函数传递给它来显式创建类型的对象,然后将该对象传递给DoWork

builder.DoWork("param1", Func<_,_> actionImpl)
Run Code Online (Sandbox Code Playgroud)