从 C# 调用 F# 代码

Posted

技术标签:

【中文标题】从 C# 调用 F# 代码【英文标题】:Call F# code from C# 【发布时间】:2010-10-03 11:21:56 【问题描述】:

我正在玩 F# 和 C#,并且想。

我设法让它在 Visual Studio 中以相反的方式工作,方法是在同一个解决方案中拥有两个项目,并将 C# 代码的引用添加到 F# 项目。完成此操作后,我可以调用 C# 代码,甚至可以在调试时单步执行。

我想要做的是来自 C# 的 F# 代码,而不是来自 F# 的 C# 代码。我在 C# 项目中添加了对 F# 项目的引用,但它没有像以前那样工作。我想知道这是否可以不手动进行。

【问题讨论】:

除非您有特定问题,否则今天从 C# 项目中添加对 F# 项目的引用“可以正常工作”。这里没有什么特别之处,因为这是 .NET 架构(与语言无关、MSIL 等)的基本承诺或好处之一。事实上,相反的情况会很奇怪。您对这笔赏金还有什么期待? 【参考方案1】:

以下是从 C# 调用 F# 的工作示例。

正如您所遇到的,我无法通过从“添加参考...项目”选项卡中进行选择来添加参考。相反,我必须手动完成,方法是在“添加引用...浏览”选项卡中浏览到 F# 程序集。

----- F# 模块 -----

// First implement a foldl function, with the signature (a->b->a) -> a -> [b] -> a
// Now use your foldl function to implement a map function, with the signature (a->b) -> [a] -> [b]
// Finally use your map function to convert an array of strings to upper case
//
// Test cases are in TestFoldMapUCase.cs
//
// Note: F# provides standard implementations of the fold and map operations, but the 
// exercise here is to build them up from primitive elements...

module FoldMapUCase.Zumbro
#light


let AlwaysTwo =
   2

let rec foldl fn seed vals = 
   match vals with
   | head :: tail -> foldl fn (fn seed head) tail
   | _ -> seed


let map fn vals =
   let gn lst x =
      fn( x ) :: lst
   List.rev (foldl gn [] vals)


let ucase vals =
   map String.uppercase vals

----- 模块的 C# 单元测试 -----

// Test cases for FoldMapUCase.fs
//
// For this example, I have written my NUnit test cases in C#.  This requires constructing some F#
// types in order to invoke the F# functions under test.


using System;
using Microsoft.FSharp.Core;
using Microsoft.FSharp.Collections;
using NUnit.Framework;

namespace FoldMapUCase

    [TestFixture]
    public class TestFoldMapUCase
    
        public TestFoldMapUCase()
                    
        

        [Test]
        public void CheckAlwaysTwo()
        
            // simple example to show how to access F# function from C#
            int n = Zumbro.AlwaysTwo;
            Assert.AreEqual(2, n);
        

        class Helper<T>
        
            public static List<T> mkList(params T[] ar)
            
                List<T> foo = List<T>.Nil;
                for (int n = ar.Length - 1; n >= 0; n--)
                    foo = List<T>.Cons(ar[n], foo);
                return foo;
            
        


        [Test]
        public void foldl1()
        
            int seed = 64;
            List<int> values = Helper<int>.mkList( 4, 2, 4 );
            FastFunc<int, FastFunc<int,int>> fn =
                FuncConvert.ToFastFunc( (Converter<int,int,int>) delegate( int a, int b )  return a/b;  );

            int result = Zumbro.foldl<int, int>( fn, seed, values);
            Assert.AreEqual(2, result);
        

        [Test]
        public void foldl0()
        
            string seed = "hi mom";
            List<string> values = Helper<string>.mkList();
            FastFunc<string, FastFunc<string, string>> fn =
                FuncConvert.ToFastFunc((Converter<string, string, string>)delegate(string a, string b)  throw new Exception("should never be invoked"); );

            string result = Zumbro.foldl<string, string>(fn, seed, values);
            Assert.AreEqual(seed, result);
        

        [Test]
        public void map()
        
            FastFunc<int, int> fn =
                FuncConvert.ToFastFunc((Converter<int, int>)delegate(int a)  return a*a; );

            List<int> vals = Helper<int>.mkList(1, 2, 3);
            List<int> res = Zumbro.map<int, int>(fn, vals);

            Assert.AreEqual(res.Length, 3);
            Assert.AreEqual(1, res.Head);
            Assert.AreEqual(4, res.Tail.Head);
            Assert.AreEqual(9, res.Tail.Tail.Head);
        

        [Test]
        public void ucase()
        
            List<string> vals = Helper<string>.mkList("arnold", "BOB", "crAIg");
            List<string> exp = Helper<string>.mkList( "ARNOLD", "BOB", "CRAIG" );
            List<string> res = Zumbro.ucase(vals);
            Assert.AreEqual(exp.Length, res.Length);
            Assert.AreEqual(exp.Head, res.Head);
            Assert.AreEqual(exp.Tail.Head, res.Tail.Head);
            Assert.AreEqual(exp.Tail.Tail.Head, res.Tail.Tail.Head);
        

    

【讨论】:

谢谢。 “我确实必须手动完成,方法是在‘添加引用...浏览’选项卡中浏览到 F# 程序集。”对我有用。【参考方案2】:

它应该“正常工作”,尽管您可能必须在 C# 的项目到项目引用工作之前构建 F# 项目(我忘记了)。

一个常见的问题来源是命名空间/模块。如果您的 F# 代码不以命名空间声明开头,它会被放入与文件名同名的模块中,例如在 C# 中,您的类型可能显示为“Program.Foo”而不仅仅是“Foo”(如果 Foo 是 Program.fs 中定义的 F# 类型)。

【讨论】:

感谢您提供有关模块名称的信息:)。 是的,我需要写一篇博客,这会引起很多混乱。 当 Fsharp 项目(dll 引用的生成器)与 Csharp(消费者项目)在同一个解决方案中时,会触发另一个问题【参考方案3】:

来自this link 他们似乎有许多可能的解决方案,但似乎最简单的一个是评论:

F# 代码:

type FCallback = delegate of int*int -> int;;
type FCallback =
  delegate of int * int -> int

let f3 (f:FCallback) a b = f.Invoke(a,b);;
val f3 : FCallback -> int -> int -> int

C#代码:

int a = Module1.f3(Module1.f2, 10, 20); // method gets converted to the delegate automatically in C#

【讨论】:

我在 val 行出现错误:val f3:FCallback -> int -> int -> int "错误 1 ​​定义中出现意外的关键字 'val'。预计在此之前或之前不完整的结构化构造或其他令牌。”【参考方案4】:

// Test.fs :

module meGlobal

type meList() = 
    member this.quicksort = function
        | [] -> []  //  if list is empty return list
        | first::rest -> 
            let smaller,larger = List.partition((>=) first) rest
        List.concat[this.quicksort smaller; [first]; this.quicksort larger]

// Test.cs :

List<int> A = new List<int>  13, 23, 7, 2 ;
meGlobal.meList S = new meGlobal.meList();

var cquicksort = Microsoft.FSharp.Core.FSharpFunc<FSharpList<IComparable>,     FSharpList<IComparable>>.ToConverter(S.quicksort);

FSharpList<IComparable> FI = ListModule.OfSeq(A.Cast<IComparable>());
var R = cquicksort(FI);

【讨论】:

以上是关于从 C# 调用 F# 代码的主要内容,如果未能解决你的问题,请参考以下文章

从 C# 代码内部访问 F# 列表

从 C# 线程调用非托管代码

从 C++ 调用 C# 代码

从后面的 C# 代码调用 JavaScript [重复]

从 C# 调用 C/C++ 代码

C#通过代码调用PowerShell