Aplikasi konsol
Tutorial ini mengajarkan Anda beberapa fitur dalam .NET dan bahasa C#. Anda akan mempelajari:
- Dasar-dasar .NET CLI
- Struktur Aplikasi Konsol C#
- Konsol I/O
- Dasar-dasar File API I/O di .NET
- Dasar-dasar Pemrograman Asinkron Berbasis Tugas di .NET
Anda akan membuat aplikasi yang membaca file teks, dan menggemakan konten file teks tersebut ke konsol. Output ke konsol diatur agar sesuai dengan membacanya dengan keras. Anda dapat mempercepat atau memperlambat kecepatan dengan menekan tombol '<' (kurang dari) atau '>' (lebih besar dari). Anda dapat menjalankan aplikasi ini di Windows, Linux, macOS, atau dalam kontainer Docker.
Ada banyak fitur dalam tutorial ini. Mari kita membangunnya satu per satu.
Prasyarat
- SDK .NET 6.
- Editor kode.
Membuat aplikasi
Langkah pertama adalah membuat aplikasi baru. Buka perintah dan buat direktori baru untuk aplikasi Anda. Jadikan direktori baru tersebut direktori saat ini. Ketik perintah dotnet new console
pada perintah. Perintah ini membuat file starter untuk aplikasi dasar "Halo Dunia".
Sebelum mulai melakukan modifikasi, mari kita jalankan aplikasi Halo Dunia yang sederhana. Setelah membuat aplikasi, ketik dotnet run
pada perintah. Perintah ini menjalankan proses pemulihan paket NuGet, membuat aplikasi yang dapat dieksekusi, dan menjalankan yang dapat dieksekusi.
Kode aplikasi Halo Dunia sederhana semuanya ada di Program.cs. Buka file tersebut dengan editor teks favorit Anda. Ganti kode di Program.cs dengan kode berikut:
namespace TeleprompterConsole;
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
Di bagian atas file, lihat pernyataan namespace
. Seperti bahasa Berorientasi Objek lainnya yang mungkin pernah Anda gunakan, C# menggunakan namespace untuk mengatur jenis. Program Halo Dunia ini tidak berbeda. Anda dapat melihat bahwa program tersebut berada di namespace dengan nama TeleprompterConsole
.
Membaca dan Menggemakan File
Fitur pertama yang ditambahkan adalah kemampuan untuk membaca file teks dan menampilkan semua teks tersebut ke konsol. Pertama, mari tambahkan file teks. Salin file sampleQuotes.txt dari repositori GitHub untuk sampel ini ke direktori proyek Anda. Ini akan berfungsi sebagai skrip untuk aplikasi Anda. Untuk informasi tentang cara mengunduh aplikasi sampel untuk tutorial ini, lihat petunjuk di Sampel dan Tutorial.
Selanjutnya, tambahkan metode berikut di kelas Program
Anda (tepat di bawah metode Main
):
static IEnumerable<string> ReadFrom(string file)
{
string? line;
using (var reader = File.OpenText(file))
{
while ((line = reader.ReadLine()) != null)
{
yield return line;
}
}
}
Metode ini adalah jenis khusus metode C# yang disebut metode iterator. Metode iterator mengembalikan urutan yang dievaluasi dengan malas. Itu berarti setiap item dalam urutan dihasilkan seperti yang diminta oleh kode yang menggunakan urutan tersebut. Metode iterator adalah metode yang berisi satu atau beberapa yield return
pernyataan. Objek yang dikembalikan oleh metode ReadFrom
berisi kode untuk menghasilkan setiap item dalam urutan. Dalam contoh ini, yang melibatkan membaca baris teks berikutnya dari file sumber, dan mengembalikan string tersebut. Setiap kali kode panggilan meminta item berikutnya dari urutan, kode membaca baris teks berikutnya dari file dan mengembalikannya. Ketika file sudah selesai dibaca, urutannya menunjukkan bahwa tidak ada item lagi.
Ada dua elemen sintaks C# yang mungkin baru bagi Anda. Pernyataan using
dalam metode ini mengelola pembersihan sumber daya. Variabel yang diinisialisasi dalam pernyataan using
(reader
, dalam contoh ini) harus mengimplementasikan antarmuka IDisposable. Antarmuka tersebut mendefinisikan satu metode, Dispose
, yang harus dipanggil saat sumber daya harus dirilis. Pengompilasi menghasilkan panggilan tersebut ketika eksekusi mencapai kurung kurawal penutup dari pernyataan using
. Kode yang dihasilkan oleh pengompilasi memastikan bahwa sumber daya dirilis bahkan jika pengecualian ditampilkan dari kode di blok yang ditentukan oleh pernyataan penggunaan.
Variabel reader
ditentukan menggunakan kata kunci var
. var
mendefinisikan variabel lokal yang diketik secara implisit. Itu berarti jenis variabel ditentukan oleh jenis waktu kompilasi dari objek yang ditetapkan ke variabel. Di sini, itu adalah nilai kembalian dari metode OpenText(String), yang merupakan objek StreamReader.
Sekarang, mari kita isi kode untuk membaca file dengan metode Main
:
var lines = ReadFrom("sampleQuotes.txt");
foreach (var line in lines)
{
Console.WriteLine(line);
}
Jalankan program (menggunakan dotnet run
) dan Anda dapat melihat setiap baris dicetak ke konsol.
Menambahkan Penundaan dan Memformat output
Apa yang Anda miliki ditampilkan terlalu cepat untuk dibaca dengan keras. Sekarang Anda perlu menambahkan penundaan dalam output. Saat memulai, Anda akan membangun beberapa kode inti yang memungkinkan pemrosesan asinkron. Namun, langkah-langkah pertama ini akan mengikuti beberapa anti-pola. Anti-pola ditunjukkan dalam komentar saat Anda menambahkan kode, dan kode akan diperbarui pada langkah selanjutnya.
Ada dua langkah untuk bagian ini. Pertama, Anda akan memperbarui metode iterator untuk menampilkan satu kata, bukan seluruh baris. Hal tersebut dilakukan dengan modifikasi ini. Ganti pernyataan yield return line;
dengan kode berikut:
var words = line.Split(' ');
foreach (var word in words)
{
yield return word + " ";
}
yield return Environment.NewLine;
Selanjutnya, Anda perlu mengubah cara menggunakan baris file, dan menambahkan penundaan setelah menulis setiap kata. Ganti pernyataan Console.WriteLine(line)
dalam metode Main
dengan blok berikut:
Console.Write(line);
if (!string.IsNullOrWhiteSpace(line))
{
var pause = Task.Delay(200);
// Synchronously waiting on a task is an
// anti-pattern. This will get fixed in later
// steps.
pause.Wait();
}
Jalankan sampel, dan periksa outputnya. Sekarang, setiap kata dicetak, diikuti dengan penundaan 200 md. Namun, output yang ditampilkan menunjukkan beberapa masalah karena file teks sumber memiliki beberapa baris yang memiliki lebih dari 80 karakter tanpa jeda baris. Tulisan tersebut akan sulit dibaca saat sedang discroll. Masalah tersebut mudah diperbaiki. Anda hanya akan melacak panjang setiap baris, dan membuat baris baru setiap kali panjang garis mencapai ambang tertentu. Deklarasikan variabel lokal setelah deklarasi words
dalam metode ReadFrom
yang menampung panjang baris:
var lineLength = 0;
Kemudian, tambahkan kode berikut setelah pernyataan yield return word + " ";
(sebelum kurung kurawal):
lineLength += word.Length + 1;
if (lineLength > 70)
{
yield return Environment.NewLine;
lineLength = 0;
}
Jalankan sampel, dan Anda akan dapat membaca dengan lantang dengan kecepatan yang telah dikonfigurasi sebelumnya.
Tugas Asinkron
Pada langkah terakhir ini, Anda akan menambahkan kode untuk menulis output secara asinkron dalam satu tugas, sambil menjalankan tugas lain untuk membaca input dari pengguna jika mereka ingin mempercepat atau memperlambat tampilan teks, atau menghentikan tampilan teks sepenuhnya. Langkah ini memiliki beberapa langkah di dalamnya dan pada akhirnya, Anda akan memiliki semua pembaruan yang Anda butuhkan. Langkah pertama adalah membuat metode pengembalian Task asinkron yang mewakili kode yang telah Anda buat sejauh ini untuk membaca dan menampilkan file.
Tambahkan metode ini ke kelas Program
Anda (diambil dari isi metode Main
Anda):
private static async Task ShowTeleprompter()
{
var words = ReadFrom("sampleQuotes.txt");
foreach (var word in words)
{
Console.Write(word);
if (!string.IsNullOrWhiteSpace(word))
{
await Task.Delay(200);
}
}
}
Anda akan melihat dua perubahan. Pertama, dalam isi metode, sebagai ganti memanggil Wait() untuk menunggu tugas selesai secara serempak, versi ini menggunakan kata kunci await
. Untuk melakukannya, Anda perlu menambahkan pengubah async
ke tanda tangan metode. Metode ini mengembalikan Task
. Perhatikan bahwa tidak ada pernyataan pengembalian yang mengembalikan objek Task
. Sebagai gantinya, objek Task
tersebut dibuat oleh kode yang dihasilkan oleh pengompilasi saat Anda menggunakan operator await
. Anda dapat membayangkan bahwa metode ini kembali ketika mencapai await
. Task
yang dikembalikan menunjukkan bahwa pekerjaan belum selesai. Metode dilanjutkan ketika tugas yang ditunggu selesai. Ketika telah dieksekusi sampai selesai, Task
yang dikembalikan menunjukkan bahwa pekerjaan telah selesai.
Kode panggilan dapat memantau Task
yang dikembalikan untuk menentukan kapan pekerjaan akan selesai.
await
Tambahkan kata kunci sebelum panggilan ke ShowTeleprompter
:
await ShowTeleprompter();
Ini mengharuskan Anda untuk mengubah Main
tanda tangan metode menjadi:
static async Task Main(string[] args)
Pelajari selengkapnya tentang async Main
metode di bagian dasar-dasar kami.
Selanjutnya, Anda perlu menulis metode asinkron kedua untuk membaca dari Konsol dan memperhatikan tombol '<' (kurang dari), '>' (lebih besar dari) dan 'X' atau 'x'. Berikut metode yang Anda tambahkan untuk tugas tersebut:
private static async Task GetInput()
{
var delay = 200;
Action work = () =>
{
do {
var key = Console.ReadKey(true);
if (key.KeyChar == '>')
{
delay -= 10;
}
else if (key.KeyChar == '<')
{
delay += 10;
}
else if (key.KeyChar == 'X' || key.KeyChar == 'x')
{
break;
}
} while (true);
};
await Task.Run(work);
}
Metode ini membuat ekspresi lambda untuk mewakili delegasi Action yang membaca kunci dari Konsol dan memodifikasi variabel lokal yang mewakili penundaan saat pengguna menekan tombol '<' (kurang dari) atau '>' (lebih besar dari). Metode delegasi selesai ketika pengguna menekan tombol 'X' atau 'x', yang memungkinkan pengguna menghentikan tampilan teks kapan saja. Metode ini menggunakan ReadKey() untuk memblokir dan menunggu pengguna menekan tombol.
Untuk menyelesaikan fitur ini, Anda perlu membuat metode pengembalian async Task
baru yang memulai kedua tugas ini (GetInput
dan ShowTeleprompter
), dan juga mengelola data bersama di antara kedua tugas ini.
Saatnya membuat kelas yang dapat menangani data bersama antara dua tugas ini. Kelas ini berisi dua properti publik: penundaan, dan tanda Done
untuk menunjukkan bahwa file telah dibaca sepenuhnya:
namespace TeleprompterConsole;
internal class TelePrompterConfig
{
public int DelayInMilliseconds { get; private set; } = 200;
public void UpdateDelay(int increment) // negative to speed up
{
var newDelay = Min(DelayInMilliseconds + increment, 1000);
newDelay = Max(newDelay, 20);
DelayInMilliseconds = newDelay;
}
public bool Done { get; private set; }
public void SetDone()
{
Done = true;
}
}
Letakkan kelas tersebut di file baru, dan sertakan kelas tersebut di namespace TeleprompterConsole
seperti yang ditunjukkan. Anda juga perlu menambahkan pernyataan using static
di bagian atas file sehingga Anda dapat mereferensikan metode Min
dan Max
tanpa menyertakan nama kelas atau namespace. Pernyataan using static
mengimpor metode dari satu kelas. Ini berbeda dengan pernyataan using
tanpa static
, yang mengimpor semua kelas dari namespace.
using static System.Math;
Selanjutnya, Anda perlu memperbarui metode ShowTeleprompter
dan GetInput
untuk menggunakan objek config
baru. Tulis satu metode pengembalian Task
terakhir async
untuk memulai kedua tugas dan keluar saat tugas pertama selesai:
private static async Task RunTeleprompter()
{
var config = new TelePrompterConfig();
var displayTask = ShowTeleprompter(config);
var speedTask = GetInput(config);
await Task.WhenAny(displayTask, speedTask);
}
Satu-satunya metode baru di sini adalah panggilan WhenAny(Task[]). Metode inin membuat Task
yang selesai segera setelah salah satu tugas dalam daftar argumennya selesai.
Selanjutnya, Anda perlu memperbarui metode ShowTeleprompter
dan GetInput
guna menggunakan objek config
untuk penundaan:
private static async Task ShowTeleprompter(TelePrompterConfig config)
{
var words = ReadFrom("sampleQuotes.txt");
foreach (var word in words)
{
Console.Write(word);
if (!string.IsNullOrWhiteSpace(word))
{
await Task.Delay(config.DelayInMilliseconds);
}
}
config.SetDone();
}
private static async Task GetInput(TelePrompterConfig config)
{
Action work = () =>
{
do {
var key = Console.ReadKey(true);
if (key.KeyChar == '>')
config.UpdateDelay(-10);
else if (key.KeyChar == '<')
config.UpdateDelay(10);
else if (key.KeyChar == 'X' || key.KeyChar == 'x')
config.SetDone();
} while (!config.Done);
};
await Task.Run(work);
}
Versi baru ShowTeleprompter
ini memanggil metode baru di kelas TeleprompterConfig
. Sekarang, Anda perlu memperbarui Main
untuk memanggil RunTeleprompter
, bukan ShowTeleprompter
:
await RunTeleprompter();
Kesimpulan
Tutorial ini menunjukkan kepada Anda beberapa fitur seputar bahasa C# dan pustaka .NET Core yang terkait dengan bekerja di aplikasi Konsol. Anda dapat membangun pengetahuan ini untuk mengeksplorasi lebih banyak tentang bahasa, dan kelas yang diperkenalkan di sini. Anda telah melihat dasar-dasar File dan Konsol I/O, penggunaan pemblokiran dan non-pemblokiran dari pemrograman asinkron berbasis Tugas, tur bahasa C# dan cara program C# diatur, dan .NET CLI.
Untuk informasi selengkapnya tentang File I/O, lihat File dan Aliran I/O. Untuk informasi selengkapnya tentang model pemrograman asinkron yang digunakan dalam tutorial ini, lihat Pemrograman Asinkron Berbasis Tugas dan Pemrograman Asinkron.