Mengonsumsi pustaka C#

Menggunakan paket NuGet dan API .NET dari F#, termasuk penanganan null di batas interoperabilitas.

Daftar isi

Topik ini membahas penggunaan pustaka .NET dari F#, baik pustaka pihak ketiga dari NuGet maupun pustaka bawaan .NET yang dirancang berorientasi objek. Pembahasannya mencakup pemuatan paket dari skrip fsx dan dari proyek, pemanggilan anggota objek gaya .NET dari kode F#, serta penanganan null pada batas interoperabilitas. Setelah topik ini dibaca, penggunaan paket NuGet melalui dua jalur sekaligus konversi null menjadi tipe option dapat dilakukan dengan pola yang aman.

Memuat paket NuGet dari skrip fsx#

Direktif #r "nuget: NamaPaket, versi" memerintahkan dotnet fsi memuat paket NuGet sebelum skrip dieksekusi. F# Interactive menyelesaikan paket dari nuget.org, menyimpannya ke cache lokal, lalu membuatnya tersedia bagi sisa skrip. Versi dipatok langsung pada direktif agar perilaku skrip tidak berubah ketika pustakanya mengeluarkan versi baru. Contoh berikut menggunakan Newtonsoft.Json, pustaka JSON berorientasi objek yang tersedia di NuGet, untuk mengubah record F# menjadi teks JSON dan sebaliknya.

fsharp baca-json.fsx
// #r memuat paket NuGet sebelum skrip dieksekusi; versinya dipatok agar tetap stabil
#r "nuget: Newtonsoft.Json, 13.0.3"
open Newtonsoft.Json

type StatistikUjian =
    { MataPelajaran: string
      JumlahPeserta: int
      RataRata: float }

let statistik =
    { MataPelajaran = "Matematika"
      JumlahPeserta = 32
      RataRata = 78.5 }

let json = JsonConvert.SerializeObject(statistik)
printfn "JSON  : %s" json

let hasil = JsonConvert.DeserializeObject<StatistikUjian>(json)
printfn "Kembali: %s, %d peserta, rata-rata %F" hasil.MataPelajaran hasil.JumlahPeserta hasil.RataRata
text Keluaran
JSON  : {"MataPelajaran":"Matematika","JumlahPeserta":32,"RataRata":78.5}
Kembali: Matematika, 32 peserta, rata-rata 78.500000

Fungsi SerializeObject menerima record dan menghasilkan teks JSON dengan nama field sebagai kunci, sedangkan DeserializeObject<StatistikUjian> mengubah teks kembali menjadi record. Keduanya merupakan metode statis pada kelas JsonConvert milik pustaka, dan F# memanggilnya seperti fungsi biasa tanpa sintaks tambahan. Alur skrip tidak memerlukan pendaftaran referensi proyek apa pun, cukup satu baris direktif di bagian atas berkas.

Memuat paket ke dalam proyek#

Proyek yang dibangun dengan dotnet memuat paket melalui elemen PackageReference pada berkas fsproj, dan elemen itu ditambahkan dengan perintah dotnet add package. Perintah dotnet new dan dotnet add package mencetak ringkasan progres yang memuat durasi serta jalur berkas, sehingga keluarannya tidak ditempelkan pada topik ini. Langkahnya adalah membuat proyek, menambahkan paket dengan versi yang dipatok, lalu mengisi kode programnya.

bash
# membuat proyek konsol F# bernama UjiNuget
dotnet new console -lang F# -o UjiNuget
# menambahkan paket NuGet dengan versi yang dipatok
dotnet add UjiNuget package Newtonsoft.Json --version 13.0.3

Hasil perintah dotnet add package dapat dilihat langsung pada berkas fsproj, yaitu satu elemen <PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> di dalam ItemGroup. Elemen itulah sumber kebenaran tunggal referensi paket pada proyek, sehingga penambahan manual pun sah dilakukan. Setiap eksekusi dotnet run memeriksa elemen tersebut dan memulihkan paket yang belum ada di cache sebelum membangun.

Berkas Program.fs berikut mengisi proyek tersebut, dengan isi yang mengikuti skrip pada bagian sebelumnya.

fsharp Program.fs
// tidak-dijalankan: berkas proyek ini dieksekusi melalui blok bash berikutnya
open Newtonsoft.Json

type StatistikUjian =
    { MataPelajaran: string
      JumlahPeserta: int
      RataRata: float }

let statistik =
    { MataPelajaran = "Matematika"
      JumlahPeserta = 32
      RataRata = 78.5 }

let json = JsonConvert.SerializeObject(statistik)
printfn "JSON  : %s" json

let hasil = JsonConvert.DeserializeObject<StatistikUjian>(json)
printfn "Kembali: %s, %d peserta, rata-rata %F" hasil.MataPelajaran hasil.JumlahPeserta hasil.RataRata
bash
# menuliskan isi Program.fs proyek dari berkas yang ditampilkan di atas
cp Program.fs UjiNuget/Program.fs
# menjalankan proyek, paket telah terpasang pada langkah sebelumnya
dotnet run --project UjiNuget
text Keluaran
JSON  : {"MataPelajaran":"Matematika","JumlahPeserta":32,"RataRata":78.5}
Kembali: Matematika, 32 peserta, rata-rata 78.500000

Isi Program.fs sama persis dengan skrip pada bagian sebelumnya, kecuali baris #r yang dihapus karena referensi paket kini diurus berkas fsproj. Perintah dotnet run membangun proyek lalu mengeksekusinya, dan hasilnya identik dengan alur skrip. Pendekatan proyek digunakan ketika kode akan diuji, dikemas, atau dibagikan, sedangkan alur fsx cocok untuk eksperimen cepat.

Pustaka bawaan .NET dari sudut pandang F##

Sebagian besar pustaka bawaan .NET dirancang berorientasi objek, dan F# memanggilnya langsung melalui notasi titik tanpa lapisan khusus. Dua contoh yang sering digunakan adalah System.Text.StringBuilder untuk menyusun teks secara bertahap dan System.Collections.Generic.List<'T> untuk daftar yang isinya boleh bertambah dan berkurang. Keduanya dirancang mutable, sehingga penggunaannya di F# memang sengaja dan terbatas pada kebutuhan yang cocok.

fsharp pustaka-bawaan.fsx
// Pustaka bawaan .NET yang berorientasi objek digunakan langsung dari F#
open System
open System.Collections.Generic
open System.Text

// StringBuilder menyusun teks secara bertahap pada satu objek mutable
let susunRingkasan(nilai: int list) =
    let papan = StringBuilder()
    papan.Append("Rekap nilai: ") |> ignore
    papan.Append(String.Join(", ", nilai |> List.map string)) |> ignore
    papan.ToString()

printfn "%s" (susunRingkasan([80; 45; 75]))

// List<'T> dari System.Collections.Generic: daftar yang boleh bertambah dan berkurang
let antrean = List<string>()
antrean.Add("Ali")
antrean.Add("Budi")
antrean.Add("Citra")
printfn "Jumlah: %d" antrean.Count
antrean.Remove("Budi") |> ignore
printfn "Sisa  : %d" antrean.Count
for nama in antrean do
    printfn "Dilayani: %s" nama
text Keluaran
Rekap nilai: 80, 45, 75
Jumlah: 3
Sisa  : 2
Dilayani: Ali
Dilayani: Citra

Member yang nilai kembaliannya tidak digunakan lebih lanjut disisihkan dengan operator |> ignore, misalnya hasil Append yang mengembalikan papan itu sendiri agar dapat dirantai. Properti Count dibaca tanpa tanda kurung sesuai bentuknya di .NET, sedangkan String.Join memperlihatkan fungsi statis .NET yang dipanggil seperti fungsi F# biasa. Untuk logika domain sehari-hari, koleksi F# seperti list dan Map tetap lebih diutamakan, sedangkan StringBuilder dan List<'T> masuk akal pada batas interoperabilitas atau pada penyusunan teks dan antrean yang memang berubah secara bertahap.

Menangani null di batas#

F# tidak memperkenalkan null pada tipe domainnya, tetapi pustaka .NET tetap mengembalikan null untuk nilai yang tidak ada. Nilai semacam itu sah di dunia .NET dan menjadi sumber error yang paling sering muncul di batas interoperabilitas. Dua perkakas F# menutup celah ini, yaitu fungsi isNull untuk memeriksa keberadaan nilai dan Option.ofObj untuk mengubah nilai yang mungkin null menjadi tipe option.

fsharp batas-null.fsx
// #r memuat paket NuGet; JObject membaca dokumen JSON berorientasi objek
#r "nuget: Newtonsoft.Json, 13.0.3"
open Newtonsoft.Json.Linq

let json = """{"Judul":null,"Isi":"Periksa ulang lampiran"}"""
let dokumen = JObject.Parse(json)

// API .NET mengembalikan null untuk field yang kosong
let judul = dokumen.Value<string>("Judul")
printfn "Judul null? %b" (isNull judul)

// Option.ofObj mengubah nilai yang mungkin null menjadi tipe Option
let judulAman = Option.ofObj judul
match judulAman with
| Some teks -> printfn "Judul: %s" teks
| None -> printfn "Judul tidak ada dalam JSON"

let penulis = dokumen.SelectToken("Penulis")
printfn "Penulis null? %b" (isNull penulis)
match Option.ofObj penulis with
| Some token -> printfn "Penulis: %s" (string token)
| None -> printfn "Field Penulis tidak ada"
text Keluaran
Judul null? true
Judul tidak ada dalam JSON
Penulis null? true
Field Penulis tidak ada

Panggilan Value<string>("Judul") mengembalikan null karena field JSON memang bernilai null, sedangkan SelectToken("Penulis") mengembalikan null karena field itu tidak ada sama sekali. Fungsi isNull memeriksa keduanya tanpa risiko error, dan Option.ofObj mengubahnya menjadi Some atau None agar keputusan atas nilai yang tidak ada dituliskan dengan match seperti biasa. Setelah dikonversi, null tidak menyebar lebih jauh ke dalam kode F#.

Ringkasan#

  • Direktif #r "nuget: ..." memuat paket pada skrip fsx, sedangkan proyek memuat paket melalui dotnet add package yang menulis PackageReference pada berkas fsproj.
  • Pustaka bawaan .NET dipanggil dari F# melalui notasi titik, dan nilai kembalian yang sengaja disisihkan diberi |> ignore.
  • isNull memeriksa keberadaan nilai dari pustaka .NET, sedangkan Option.ofObj mengubahnya menjadi Some atau None agar null tidak menyebar ke dalam kode F#.

Referensi#