如果你也會C#,那不妨瞭解下F#(5):模塊、與C#互相調用

来源:http://www.cnblogs.com/hjklin/archive/2016/09/04/fs-for-cs-dev-5.html
-Advertisement-
Play Games

介紹創建F#項目,F#中的模塊以及與C#項目互相引用需要註意的問題。 ...


F# 項目

在之前的幾篇文章介紹的代碼都在交互視窗(fsi.exe)里運行,但平常開發的軟體程式可能含有大類類型和函數定義,代碼不可能都在一個文件里。下麵我們來看VS里提供的F#項目模板。

F#項目模板有以下幾種類型(以VS2015為例): F#項目模板

  • Silverlight庫創建Silverlight的類庫
  • 教程模板是一個控制台應用程式,裡面包含了F#的示例,可通過這個項目快速瞭解F#相關內容。
  • “可移植庫”則可創建用於多平臺的庫,支持的平臺在括弧里說明。
  • ”用於創建類庫
  • 控制台應用程式”大家就熟悉了。
  • 安卓項目為安裝了Xamarin創建的,請忽略。

我們創建一個控制台應用程式來說明,下圖為程式的Program.fs文件及運行結果:F#控制台程式及運行結果

我們添加一行代碼(圖中藍框中)防止運行結束自動退出,這個應用程式預設是把參數列印出來,而運行時參數為空,所以結果為一空數組([||])。

其中ignore函數用於丟棄System.Console.ReadKey()結果

現在項目中除了AssemblyInfo.fs外,只有Program.fs一個文件,下麵我們先瞭解模塊的相關信息再創建其他文件。

模塊

模塊簡介

模塊(Module)是F#程式代碼的基本組織單位。預設情況下,每個F#代碼文件(尾碼為.fs)對應一個模塊,且必須在文件開頭指定模塊名稱。

創建模塊

我們創建File1.fs文件時,預設會在開頭添加module File1,當然也可自己改成其他名稱。

module File1
let x = 1

在其他模塊中使用File1.x進行訪問。

文件順序

F#項目中的文件是有順序要求的,在上面的文件無法訪問下麵的模塊。我們可以使用Alt+上/下箭頭進行調整文件順序,或在文件上點擊右鍵進行操作: F#代碼順序

嵌套模塊

模塊中可嵌套模塊,但定義內層模塊需要在模塊名後使用等號(=),且內層模塊的內容必須比它的上層模塊縮進一級。

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

使用模塊

若想不使用模塊名訪問模塊中的值時,則可使用open關鍵字進行打開。但有兩個需要註意的地方:

強制顯示訪問

在上一章介紹的集合模塊中,我們從未使用open List或者open Seq這樣的操作。

使用F12轉到Seq的代碼定義文件可以發現Seq模塊使用了
[<RequireQualifiedAccess>](強制顯示訪問)

附加了此特性的模塊在使用時必須使用模塊名訪問,因為幾個集合模塊中有大部分函數名稱是相同的,若設置此特性而可同時打開了多個模塊,則函數名稱將會衝突。

自動打開

而我們在使用printfnignore函數時,均不需要打開相關模塊,是因為在他們所屬模塊附加了[<AutoOpen>](自動打開)的特性。像Operators模塊里有我們常用的運算符,為了方便使用,添加了自動打開的特性。

我們在自定義模塊時可根據需要使用這兩個特性。

命名空間

命名空間(Namespace)和模塊類似,不同的是命名空間里不能直接定義值,只能定義類型。(與C#中的命名空間一樣,可以想象我們無法在C#的命名空間中直接定義一個方法,而需要首先定義一個類。)

但F#中的命名空間不能像模塊那樣嵌套,但可以在同一文件中定義多個命名空間。

namespace PlayingCards
type Suit = Spade | Club | Diamond | Heart

namespace PlayingCards.Poker
type PokerPlayer = {Name:string; Money:int; Position:int}

上面的代碼在一個文件中使用兩個命名空間分別定義了一個類型。

其中Suit可區分聯合(Discriminated Union)類型;PokerPlayer記錄(Record)類型。將在下一篇介紹。

應用程式入口

在F#中,程式從程式集的最後一個文件開始執行,而且必須是一個模塊。但最後一個模塊的名稱可省略

也可以使用[<EntryPoint>]特性應用於最後一個代碼文件的最後一個函數,使其成為程式入口點而無需顯示調用。

可查看控制台應用程式項目的模板:

[<EntryPoint>]
let main argv =     
    printfn "%A" argv
    0

main函數的參數是一個數組(通常可自定義為字元串數組),是應用程式的運行參數,返回的整數則為程式的退出代碼(exit code)。

若不使用[<EntryPoint>],則需要在最後調用該函數,否則並不會自動調用該函數。

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

控制台應用程式通常在結束之前使用System.Console.ReadKey()方法來防止運行完成自動退出。

擴展模塊

可以通過創建一個同名模塊,在其中添加值來對原有模塊進行擴展。

在介紹常用函數時,我們提到Seq模塊沒有提供rev函數,現在自己實現以Seq模塊進行擴展

open System.Collections.Generic
module 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框架中的泛型集合類型(System.Collections.Generic.Stack<T>)。

與C#互相調用

F#代碼和C#代碼(包括VB.NET)一樣,都編譯成MSIL,在CLR運行。(可參考文章《.NET框架》)所以,兩種語言之間可以方便地互相調用。

程式集的引用大家都熟悉,但C#和F#中又有一些獨立的東西不能互相使用,下麵簡單介紹一下在互相調用中常見的問題。

F#調用C#代碼

本節涉及操作需要創建兩個項目,一個C#的類庫項目,一個F#的控制台項目。然後F#項目引用C#項目。

dynamic:在F#中訪問C#的動態類型

在.NET4.0,C#引入了dynamic關鍵字使得可以像使用動態語言一樣來使用C#。但在F#中並不支持dynamic關鍵字和動態類型,在引用C#編譯的程式集時,則變成了Object類型。

我們知道dynamicMicrosoft.CSharp.dll程式集中實現,在F#中可以通過引用此程式集,通過反射等操作自己實現對動態類型及屬性的訪問。

而我在平常一般使用第三方庫FSharp.Interop.Dynamic(Nuget)。代碼示例:

//C#代碼,命名空間CSharpForFSharp
public class CSharpClass
{
  public dynamic TestDynamic()
  {
    return "5566";
  }
}

在F#中調用:

//F#代碼,位於F#項目的Program.fs
open FSharp.Interop.Dynamic
open CSharpForFSharp            //C#項目中的命名空間
[<EntryPoint>]
let main argv =     
    let cc = CSharpClass()
    let str = cc.TestDynamic()    
    printfn "%A" (str?Length)   //使用?替代.
    System.Console.Read()|>ignore
    0

打開FSharp.Interop.Dynamic命名空間,F#中可使用?來訪問動態類型的屬性和方法。

調用帶有 refout 參數的函數

在C#中,有refout兩個關鍵字來修飾函數的參數,使函數可以進行引用傳遞和返回多個值。若要在F#中調用,則有一些不同。

帶有ref參數或者out參數的函數,因為參數值可能在函數中發生改變,需要在F#先定義一個可變值類型,並使用定址操作符(&進行傳入。

// C#代碼,位於命名空間CSharpForFSharp
public class CSharpClass
{
    public static bool OutRefParams(out int x, ref int y)
    {
        x = 100;
        y = y * y;
        return true;
    }
}

在F#中調用:

// F#代碼,位於F#項目的Program.fs
open CSharpForFSharp
let mutable x,y = 0,0
CSharpClass.OutRefParams(&x,&y) 

返回true並對xy進行了改變。

帶有out的參數在C#中可以使用未賦值的變數傳入,所以在F#中除了定址傳入的方法,還可以直接忽略該參數,則該函數在F#中成為了多返回值(即返回tuple)的形式:

let successful, result = Int32.TryParse(str)

Int32.TryParse返回了兩個值,第一個總是函數返回值,而後是out參數。

柯里化C#的方法

因為C#中的函數無論有多少個參數,在F#中調用時都視為一個tuple參數,所以無法柯里化和使用函數管道符(|>)操作。

在F#中可以使用FuncConvert類將.NET中的函數轉換成F#中的函數。

let join : string*string list -> string = System.String.Join
let 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

以上代碼將System.String.Join轉化為F#中的函數,因為該方法具有多個重載,所以第一行代碼用來指定一個要轉換的重載。

其實FuncConvert類也可以在C#中使用,需要添加FSharp.Core程式集,有興趣的可以自己嘗試。

C#調用F#代碼

本節涉及操作需要創建兩個項目,一個F#的類庫項目,一個C#的控制台項目。然後C#項目引用F#項目,因為涉及到F#中獨有類型,還需要引用FSharp.Core程式集。

若要在UWP項目中引用F#項目,需要通過“可移植庫”模板創建項目。

因為C#中的類型比F#少了很多,所以很多C#不支持的類型均使用來代替,使用時只需像使用類一樣使用它就行了。而模塊,在C#中則為靜態類

F#中的函數

需要註意的是,若在F#將函數作為參數或返回值,則F#中的函數在C#中將會變成

FSharpFunc<_,_>對象(位於FSharp.Core程式集的Microsoft.FSharp.Core命名空間)。

//F# 代碼,位於TestModule模塊
open System
type MathUtilities =
    static member GetAdder() =
        (fun x y z -> Int32.Parse(x) + Int32.Parse(y) + Int32.Parse(z))

GetAdder函數返回一個將三個字元串轉成int再相加的函數,在C#中調用此函數:

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

F#中的string -> string -> string -> int類型函數在C#中變成了FSharpFunc <string, FSharpFunc <string, FSharpFunc <string, int>>>

這是因為C#中的不支持函數柯里化,如果F#中的函數需要更多的參數,在C#中調用就很麻煩了。雖然在F#使用很方便,但若需要編寫供C#使用的程式集,儘量不要使用這些功能。

命名規範

通過上面的瞭解,至少可以簡單地使用F#和C#互相調用。但有個地方可能使有強迫症的程式員很難受:F#模塊中的函數命名使用的是駝峰式(camelCase),在C#中類的方法則使用PascalCase命名規範。

F#模塊在編譯成靜態類後,在C#中使用變得不一致。在F#中提供了CompiledName特性用來指定編譯後的名稱

在第一篇中提到的F#中可用“`` ``”來使任何字元串作為變數(值)的名稱,若想在C#中調用這類值(不符合變數命名規則),也需要用CompiledName指定編譯後的名稱,否則無法調用。

module TestModule
[<CompiledName("Add")>]
let add = fun a b -> a+b
[<CompiledName("IsSeven")>]
let ``7?`` i = i % 7 = 0

在C#中調用:

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

本文發表於博客園。 轉載請註明源鏈接:http://www.cnblogs.com/hjklin/p/fs-for-cs-dev-4.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Smobiler是一個在VS環境中使用.Net語言來開發APP的開發平臺,也許比Xamarin更方便 ...
  • 經過近一步完善調整,現將本系統源碼正式開放,定名為:EasyuiAdminFramework,另外EasyuiAdminTemplate及EasyuiFlatTheme也一併開源 項目主頁:http://git.oschina.net/gudufy/easyui-admin-framework 技術 ...
  • 最近研究程式的優化,壓縮html可以減小html的體積,有利於提高頁面的相應速度。在webform的basepage中添加如下代碼: 具體到相應項目當中,還是需要調試以下,以免過濾需要輸出的內容。 ...
  • 對於跨平臺的.netCore來說,讓它的程式運行在Linux系統上已經成為必然,也是一種趨勢,畢竟我們的很多服務都放在linux伺服器上(redis,mongodb,myql,fastDFS,lucene),而我們希望與這些組件服務通訊,需要使用的代碼為java,python等,而這些都不是我們擅長 ...
  • 1、 將基礎類型轉為byte數組存儲 2.C#中結構體 與 位元組流 相互轉化 3. C# 結構體位元組對齊 在上述結構體與位元組流轉換第二種方法中,獲取結構體長度int size = Marshal.SizeOf(Mystruct);,並不是13,而是16。在記憶體特定類型數據結構起始地址通常有一定的對齊 ...
  • 1、toastr http://www.jq22.com/jquery-info476 2、jquery1.11.1 checkbox前端js代碼: 單獨使用attr方法checked屬性不改變,單獨使用prop方法屬性改變,頁面checkbox不打勾,兩者配合就沒問題,可能是版本問題 3、jque ...
  • 開始接觸 LINQ 序 在此之前曾發表過三篇關於 LINQ 的隨筆: 進階:《LINQ 標準查詢操作概述》(強烈推薦) 技巧:《Linq To Objects - 如何操作字元串》 和 《Linq To Objects - 如何操作文件目錄》 現在,自己打算再整理一篇關於 LINQ 入門的隨筆,也是 ...
  • DataGridView:顯示數據表,通過此控制項中可以實現連接資料庫,實現數據的增刪改查 一、後臺數據綁定: List<xxx> list = new List<xxx>(); dataGridView1.DataSource = list; //設置不自動生成列,此屬性在屬性面板中沒有 dataG ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...