推广 热搜: csgo  vue  angelababy  2023  gps  新车  htc  落地  app  p2p 

如果你也会C#,那不妨了解下F#(5):模块、与C#互相调用

   2023-08-29 网络整理佚名2130
核心提示:我们创建一个控制台应用程序来说明,下图为程序的.而我们在使用和函数时,均不需要打开相关模块,是因为在他们所属模块附加了[](自动打开)的特性。我们在自定义模块时可根据需要使用这两个特性。可查看控制台应用程序项目的模板:若不使用[],则需要在最后调用该函数,否则并不会自动调用该函数。通过上面的了解,至少可以简单地使用F#和C#互相调用。

F# 项目

前面几篇文章介绍的代码都是在交互窗口(fsi.exe)中运行的,但通常开发的软件程序可能会包含大量的类型和函数定义,代码不可能全部在一个文件中。 我们看一下VS中提供的F#项目模板。

F#项目模板有以下几种类型(以为例):

我们创建一个控制台应用程序来进行说明,下图显示了程序的.fs文件以及运行结果:

我们添加一行代码(图中蓝色框中)来防止运行结束时自动退出。 该应用程序默认打印出参数,并且运行时参数为空,因此结果是一个空数组([||])。

其中该函数用于丢弃 ..() 结果。

现在项目中除了.fs之外就只有.fs文件了。 我们先了解一下模块的相关信息,然后再创建其他文件。

模块模块介绍

模块 ( ) 是 F# 程序代码的基本组织单元。 默认情况下,每个F#代码文件(后缀.fs)对应一个模块,并且必须在文件开头指定模块名称。

创建模块

当我们创建File1.fs文件时,默认会添加在开头,当然你可以自己改成别的名字。

module File1let x = 1

使用File1.x在其他模块中访问。

文件顺序

F#项目中的文件是按顺序需要的,上面的文件不能访问下面的模块。 我们可以使用Alt+上/下箭头来调整文件顺序,或者右键单击文件进行操作:

嵌套模块

模块内可以嵌套模块,但模块名称后必须使用等号(=)来定义内部模块,并且内部模块的内容必须比其上层模块缩进一级。

module TopLevelModel        module NestedModule =   //第一层嵌套模块
    let i = 1
    module NestedModuleInNestedModule =  //第二层嵌套模块
        let i = 2

使用模块

如果想不使用模块名来访问模块中的值,可以使用open关键字来打开。 但有两个注意事项:

强制显示访问

在上一章介绍的集合模块中,我们从未使用过open List或open Seq之类的操作。

使用F12进入Seq的代码定义文件,可以发现Seq模块使用

[ ](强制显示访问)。

具有该功能的模块在使用时必须通过模块名来访问,因为几个集合模块中的函数名大多是相同的,如果设置了该功能并且同时打开多个模块,函数名会发生冲突。

自动打开

当我们使用and函数时,不需要打开相关模块,因为[](自动打开)的功能都被添加到了所属的模块中。 例如模块中有常用的操作符,为了使用方便,增加了自动开启的功能。

我们在定制模块时可以根据需要使用这两个功能。

命名空间

命名空间()类似于模块,不同的是命名空间中不能直接定义值,只能定义类型。 (就像C#中的命名空间一样,可以想象,我们不能直接在C#的命名空间中定义方法,而是需要先定义一个类。)

但F#中的命名空间不能像模块一样嵌套,但可以在同一个文件中定义多个命名空间。

namespace PlayingCardstype Suit = Spade | Club | Diamond | Heartnamespace PlayingCards.Pokertype PokerPlayer = {Name:string; Money:int; Position:int}

上面的代码使用两个命名空间定义文件中的类型。

其中,Suit是可区分的联合(U​​nion)类型; 它是一个()类型。 将在下一篇文章中介绍。

申请入口

在 F# 中,程序从程序集中的最后一个文件开始执行,该文件必须是模块。 但最后一个模块的名称可以省略。

还可以使用 [] 属性应用于最后一个代码文件的最后一个函数,使其成为程序入口点而无需显式调用。

可以查看控制台应用程序项目的模板:

[]let main argv =     
    printfn "%A" argv    0

main函数的参数是一个数组(通常可定制为字符串数组),它是应用程序的运行参数,返回的整数是程序的退出代码(exit code)。

如果不使用[],则需要在最后调用该函数,否则该函数不会自动调用。

let main (argv:string[]) = 
    printfn "%A" argv
    System.Console.ReadKey(true) |> ignore    0main [||]

控制台应用程序通常在结束之前使用 ..() 方法,以防止运行完成后自动退出。

扩展模块

可以通过创建同名模块并向其添加值来扩展现有模块。

在介绍常用函数时,我们提到Seq模块没有提供rev函数,现在我们自己实现来扩展Seq模块。

open System.Collections.Genericmodule Seq =    /// 反转Seq中的元素
    let rec rev (s : seq<'a>) =        let stack = new Stack<'a>()
        s |> Seq.iter stack.Push
        seq {            while stack.Count > 0 do
                yield stack.Pop()
        }

使用.NET框架中的通用堆栈集合类型(...Stack)。

用C#互相打电话

F# 代码与 C# 代码(包括 VB.NET)一样,被编译为 MSIL 并在 CLR 上运行。 (参考《.NET 》一文)因此,两种语言可以轻松地相互调用。

程序集引用大家都很熟悉,但是C#和F#中有些独立的东西是不能互相使用的。 下面简单介绍一下通话中常见的问题。

F# 调用 C# 代码

本节涉及两个项目的创建,一个C#类库项目和一个F#控制台项目。 然后,F# 项目引用 C# 项目。

: 在 F# 中访问 C# 的动态类型

在.NET4.0中,C#引入了关键字,使得C#可以像动态语言一样使用。 但是,F# 中不支持关键字和动态类型。 当引用由 C# 编译的程序集时,它就成为一种类型。

我们知道它是在..dll 程序集中实现的。 在F#中,您可以引用这个程序集,通过反射等操作自行实现对动态类型和属性的访问。

我通常使用第三方库..(Nuget)。 代码示例:

//C#代码,命名空间CSharpForFSharppublic class CSharpClass{  public dynamic TestDynamic()  {    return "5566";
  }
}

在 F# 中调用:

//F#代码,位于F#项目的Program.fsopen FSharp.Interop.Dynamicopen CSharpForFSharp            //C#项目中的命名空间[]let main argv =     
    let cc = CSharpClass()    let str = cc.TestDynamic()    
    printfn "%A" (str?Length)   //使用?替代.
    System.Console.Read()|>ignore    0

打开..命名空间,你可以使用? 在 F# 中访问动态类型的属性和方法。

使用 ref 和 out 参数调用函数

在C#中,有两个关键字ref和out来修改函数的参数,使得函数可以按引用传递并返回多个值。 在 F# 中调用,存在一些差异。

对于带有ref参数或out参数的函数,由于函数中参数值可能会发生变化,所以需要在F#中定义一个变量值类型,并使用寻址运算符(&)传入。

// C#代码,位于命名空间CSharpForFSharppublic class CSharpClass{    public static bool OutRefParams(out int x, ref int y)    {
        x = 100;
        y = y * y;        return true;
    }
}

在 F# 中调用:

// F#代码,位于F#项目的Program.fsopen CSharpForFSharplet mutable x,y = 0,0CSharpClass.OutRefParams(&x,&y) 

返回 true,并且 x 和 y 已更改。

不带out的参数在C#中可以使用未赋值的变量传入,所以在F#中除了对传入的方法进行寻址外,还可以直接忽略参数,那么函数就变成了F#中的多返回值(即返回元组) )的形式:

let successful, result = Int32.TryParse(str)

Int32。 返回两个值,第一个始终是函数返回值,然后是输出参数。

C# 柯里化方法

因为C#中的函数无论有多少个参数,在F#中调用时都被视为元组参数,所以不能用函数管道字符(|>)进行柯里化和操作。

在 F# 中,您可以使用类将 .NET 中的函数转换为 F# 中的函数。

let join : string*string list -> string = System.String.Joinlet curryJoin = FuncConvert.FuncFromTupled join[ 1..10 ]
|> List.map string|> curryJoin "*"                // "1*2*3*4*5*6*7*8*9*10"let joinStar = curryJoin "*"    // joinStar类型为:string list -> string

上面的代码将..Join转换为F#中的函数,因为该方法有多个重载,所以第一行代码用于指定要转换的重载。

其实该类在C#中也可以使用,需要添加.Core程序集,有兴趣的可以自己尝试一下。

C# 调用 F# 代码

本节涉及创建两个项目,一个F#类库项目和一个C#控制台项目。 然后C#项目引用F#项目,因为涉及到F#中特有的类型,需要引用.Core程序集。

要在 UWP 项目中引用 F# 项目,需要通过“可移植库”模板创建该项目。

因为C#中的类型比F#中少很多,很多C#不支持的类型都被类替代了,使用时只需要像类一样使用即可。 另一方面,模块是 C# 中的静态类。

F# 中的函数

需要注意的是,如果在F#中使用函数作为参数或返回值,那么F#中的函数就会变成

对象(位于 .Core 程序集的 ..Core 命名空间中)。

//F# 代码,位于TestModule模块open Systemtype MathUtilities =    static member GetAdder() =
        (fun x y z -> Int32.Parse(x) + Int32.Parse(y) + Int32.Parse(z))

该函数返回一个将三个字符串转换为整数并将它们相加的函数。 在 C# 中调用此函数:

FSharpFunc<string, FSharpFunc<string, FSharpFunc<string, int>>> ss = MathUtilities.GetAdder();var ret = ss.Invoke("123").Invoke("45").Invoke("67");

F# 中的 -> -> -> int 类型函数在 C# 中变为 >>。

这是因为 C# 不支持函数柯里化。 如果F#中的函数需要较多的参数,那么在C#中调用就会很麻烦。 虽然在F#中使用起来非常方便,但如果需要为C#编写程序集,尽量不要使用这些函数。

命名约定

通过上面的理解,至少你可以简单的使用F#和C#来互相调用了。 但有一个地方可能会让有强迫症的程序员感到为难:F#模块中的函数名使用(),C#中的类方法使用命名约定。

F# 模块在编译为静态类后在 C# 中使用时会变得不一致。 F# 中提供了指定编译名称的功能。

在第一篇文章中提到的F​​#中,可以使用“````”来使任何字符串作为变量(值)的名称。 如果要在C#中调用该类型的值(不符合变量命名规则),还需要使用 指定编译名称,否则无法调用。

module TestModule[]let add = fun a b -> a+b[]let ``7?`` i = i % 7 = 0

在 C# 中调用:

int i = TestModule.Add(3,4);var b = TestModule.IsSeven(7);

相关文章:

 
反对 0举报 0 收藏 0 打赏 0评论 0
 
更多>同类资讯
推荐图文
推荐资讯
点击排行
网站首页  |  关于我们  |  联系方式  |  使用协议  |  版权隐私  |  网站地图  |  排名推广  |  广告服务  |  积分换礼  |  网站留言  |  RSS订阅  |  违规举报
Powered By DESTOON