简体   繁体   中英

Call F# function from C# passing function as a parameter

I have the following F# function

let Fetch logger id = 
    logger "string1" "string2"
    // search a database with the id and return a result

In my C# class I want to call the Fetch F# function mocking out the logger function. So I have the following C# function as the mocker.

void Print(string x, string y) { // do nothing }

I'm trying to call the F# function from a C# class with the following.

var _logger = FuncConvert.ToFSharpFunc<string, string>(Print);
var _result = Fetch(logger, 3);

The problem with FuncConvert.ToFSharpFunc is that takes only one type argument. When I change the Fetch F# logger function to the following it works fine when I use ToFSharpFunc(Print) where the C# Print function also takes in one string.

let Fetch logger id = 
    logger "string1"
    // search a database with the id and return a result

Anyone got ideas?

Short Version

var tupleLogger = FuncConvert.ToFSharpFunc<Tuple<string,string>>(t=>Print(t.Item1,t.Item2));
var logger = FuncConvert.FuncFromTupled(tupleLogger);

MyOtheProject.MyModule.Fetch(logger, 3);

Long Version

F# functions only accept one argument. Multiple arguments essentially create nested functions. You need to do the same on C#'s side.

Check the type of the logger parameter in C# with Intellisense. It's

Microsoft.FSharp.Core.FSharpFunc<string, Microsoft.FSharp.Core.FSharpFunc<string, a>>

FuncConvert.ToFSharpFunc can't create this. FuncFromTupled though can create this from an FSharpFunc that takes a Tuple with multiple fields as an argument.

That's something that can be created by ToFsharpFunc :

FSharpFunc<Tuple<string,string>,Unit> tupleLogger = FuncConvert.ToFSharpFunc<Tuple<string,string>>(
       t=> Print(t.Item1,t.Item2));

or

var tupleLogger = FuncConvert.ToFSharpFunc<Tuple<string,string>>(t=> Print(t.Item1,t.Item2));

As FuncFromTupled 's description says, it's A utility function to convert function values from tupled to curried form. . tupleLogger is a tupled form that we need to convert to a curried form:

var logger = FuncConvert.FuncFromTupled(tupleLogger);

The resulting code looks like:

var tupleLogger = FuncConvert.ToFSharpFunc<Tuple<string,string>>(t=>Print(t.Item1,t.Item2));
var logger = FuncConvert.FuncFromTupled(tupleLogger);

MyOtheProject.MyModule.Fetch(logger, 3);

You could create a utility function to combine the two conversions :

public static class MyFuncConvert
{
    public static FSharpFunc<T1, FSharpFunc<T2, Unit>> ToFSharpFunc<T1, T2>(Action<T1, T2> action)
    {
        var tupled = FuncConvert.ToFSharpFunc<Tuple<T1, T2>>(
                                  t => action(t.Item1, t.Item2));
        var curried = FuncConvert.FuncFromTupled(tupled);
        return curried;
    }
}

Which would allow you to write :

var logger = MyFuncConvert.ToFSharpFunc<string,string>(Print);

F# functions can have only one argument. When you have for example: logger "string1" "string2" the expression logger "string1" creates another function with "string1" inside it which then can take "string2" as an argument. So to convert this you can create such helper method:

public static class FuncConvertExt
{
    public static FSharpFunc<T1, FSharpFunc<T2, Unit>> Create<T1, T2>(Action<T1, T2> action)
    {
        Converter<T1, FSharpFunc<T2, Unit>> conv = value1 =>
        {
            return FuncConvert.ToFSharpFunc<T2>(value2 => action(value1, value2));
        };

        return FSharpFunc<T1, FSharpFunc<T2, Unit>>.FromConverter(conv);
    }
}     

Then you can do this:

var func = FuncConvertExt.Create<string, string>(Print);
func.Invoke("aaa").Invoke("bbb");

More info: https://blogs.msdn.microsoft.com/jaredpar/2010/07/27/converting-system-funct1-tn-to-fsharpfuncttresult/

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM