Giai Đoạn 1: C# Cơ Bản — Bắt Đầu với C#

Chào Mừng Đến Giai Đoạn 1
Chào mừng đến với giai đoạn đầu tiên trong hành trình học C# của bạn! Nếu bạn đến từ Python, Java, JavaScript hay ngôn ngữ khác, bạn sẽ tìm thấy nhiều khái niệm quen thuộc ở đây — cùng với một số điểm thú vị. C# đã phát triển nhanh chóng, và C# hiện đại (phiên bản 10–12) là một ngôn ngữ thực sự thanh lịch.
Mục tiêu của giai đoạn này là giúp bạn quen với cú pháp C#, hiểu cách .NET tiếp cận các khái niệm lập trình cơ bản, và thiết lập môi trường phát triển hoạt động.
Đây không phải là hướng dẫn "biến là gì?". Chúng ta sẽ tập trung vào cú pháp đặc trưng của C#, hệ thống kiểu .NET, và các tính năng hiện đại phân biệt C# với các ngôn ngữ bạn đã biết.
Cam kết thời gian: 2 tuần, 1–2 giờ mỗi ngày
Điều kiện tiên quyết: Kinh nghiệm lập trình với bất kỳ ngôn ngữ nào
Bạn Sẽ Học Được Gì
Khi kết thúc Giai Đoạn 1, bạn sẽ có thể:
✅ Thiết lập môi trường phát triển .NET
✅ Viết và chạy chương trình C# với top-level statements
✅ Hiểu value types vs reference types của C#
✅ Sử dụng nullable reference types cho an toàn null
✅ Viết control flow với switch expressions và pattern matching hiện đại
✅ Định nghĩa methods với các modifier tham số khác nhau
✅ Làm việc với strings, string interpolation và raw string literals
✅ Tạo classes, records và properties cơ bản
Thiết Lập Môi Trường
1. Cài Đặt .NET SDK
Tải .NET SDK từ trang chính thức:
Khuyến nghị: .NET 8 LTS (Long-Term Support)
# Kiểm tra cài đặt
dotnet --version # Nên hiển thị 8.x.x
# Liệt kê các SDK đã cài đặt
dotnet --list-sdksTrên macOS với Homebrew:
brew install dotnetTrên Linux (Ubuntu/Debian):
sudo apt-get update && sudo apt-get install -y dotnet-sdk-8.02. Chọn IDE
Visual Studio Code + C# Dev Kit (khuyến nghị cho đa nền tảng):
- Miễn phí, nhẹ, hỗ trợ .NET xuất sắc
- Cài extension: C# Dev Kit (Microsoft)
- Cài extension: .NET Install Tool
Visual Studio 2022 (Windows/macOS):
- IDE đầy đủ tính năng, phiên bản Community miễn phí
- Trải nghiệm debug và refactoring tốt nhất
- Tải về: visualstudio.microsoft.com
JetBrains Rider (đa nền tảng):
- Refactoring và phân tích code tốt nhất
- Trả phí (dùng thử 30 ngày miễn phí)
3. Tạo Dự Án Đầu Tiên
# Tạo ứng dụng console mới
dotnet new console -n MyFirstApp
cd MyFirstApp
# Chạy
dotnet runFile Program.cs được tạo ra sử dụng top-level statements (C# 9+):
// Program.cs — không cần class hay method Main!
Console.WriteLine("Hello, C#!");Đây là cải tiến chất lượng lớn so với C# cũ, khi bạn phải viết:
// Phong cách cũ (vẫn hợp lệ, nhưng verbose)
namespace MyFirstApp;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello, C#!");
}
}Cả hai đều hợp lệ. Series này sử dụng phong cách hiện đại.
Hệ Thống Kiểu .NET
C# là ngôn ngữ kiểu tĩnh và kiểu mạnh. Mỗi biến có một kiểu, được kiểm tra tại thời điểm biên dịch.
Value Types vs Reference Types
Đây là khái niệm cơ bản nhất trong C#. Không giống Python hay JavaScript nơi mọi thứ đều là object, C# phân biệt giữa hai loại:
Value types lưu trữ dữ liệu trực tiếp trên stack. Sao chép value type sẽ sao chép dữ liệu:
int a = 10;
int b = a; // b nhận một bản sao của giá trị
b = 20;
Console.WriteLine(a); // 10 — không thay đổiReference types lưu trữ tham chiếu (con trỏ) trên stack; dữ liệu thực sự nằm trên heap. Sao chép reference type sẽ sao chép tham chiếu, không phải dữ liệu:
var list1 = new List<int> { 1, 2, 3 };
var list2 = list1; // list2 trỏ đến cùng list
list2.Add(4);
Console.WriteLine(list1.Count); // 4 — cả hai biến đều thấy sự thay đổiCác Value Types Tích Hợp Sẵn
// Kiểu số nguyên
byte b = 255; // 8-bit không dấu: 0 đến 255
short s = 32_000; // 16-bit có dấu
int i = 2_000_000; // 32-bit có dấu (phổ biến nhất)
long l = 10_000_000_000L; // 64-bit có dấu
uint u = 4_000_000_000u; // 32-bit không dấu
// Kiểu số thực
float f = 3.14f; // 32-bit (dùng hậu tố 'f')
double d = 3.14159265; // 64-bit (mặc định cho số thực)
decimal m = 19.99m; // 128-bit, chính xác — dùng cho tiền!
// Các kiểu value khác
bool flag = true;
char c = 'A'; // Ký tự UnicodeMẹo cho dev Python/JS: Dùng
intcho số nguyên,doublecho số thực thông thường, vàdecimalcho tính toán tài chính (tránh vấn đề độ chính xác floating-point).
Suy Luận Kiểu với var
C# hỗ trợ suy luận kiểu cục bộ:
var name = "Alice"; // string
var count = 42; // int
var price = 9.99m; // decimal
var items = new List<string>(); // List<string>var là suy luận tại thời điểm biên dịch — kiểu được cố định tại thời điểm biên dịch. Dùng khi kiểu rõ ràng từ vế phải. Tránh var khi kiểu không rõ:
var result = GetData(); // Kiểu gì? Tránh điều nàyNullable Reference Types
Một trong những tính năng hiện đại mạnh mẽ nhất của C#. Bật nó trong file .csproj:
<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>Bây giờ compiler theo dõi khả năng null:
string name = "Alice"; // Không thể là null
string? nickname = null; // Có thể là null (chú ý dấu '?')
// Compiler cảnh báo trước khi dereferencing null
Console.WriteLine(name.Length); // OK
Console.WriteLine(nickname.Length); // Cảnh báo: có thể là null reference
// Các pattern an toàn
Console.WriteLine(nickname?.Length ?? 0); // Null-conditional + null-coalescingĐiều này phát hiện null reference exceptions tại thời điểm biên dịch thay vì runtime — cải tiến lớn so với Java hay Python.
Biến và Hằng Số
// Khai báo và khởi tạo
int count = 0;
string message = "Hello";
// Được suy luận
var user = "Alice";
// Hằng số (tại thời điểm biên dịch, phải là literal)
const int MaxRetries = 3;
const string AppName = "MyApp";
// Readonly (tại runtime, có thể được tính toán)
readonly DateTime startTime = DateTime.Now;Quy ước đặt tên trong C#:
| Thành phần | Quy ước | Ví dụ |
|---|---|---|
| Biến | camelCase | userName, totalCount |
| Hằng số | PascalCase | MaxRetries, DefaultTimeout |
| Methods | PascalCase | GetUser(), CalculateTotal() |
| Classes | PascalCase | UserService, OrderItem |
| Interfaces | IPascalCase | IRepository, ILogger |
| Private fields | _camelCase | _name, _count |
Điểm khác biệt quan trọng với Java: C# dùng
PascalCasecho hằng số và methods công khai, không phảiUPPER_SNAKE_CASEhaycamelCase.
Control Flow
if / else
int score = 85;
if (score >= 90)
Console.WriteLine("Điểm: A");
else if (score >= 80)
Console.WriteLine("Điểm: B");
else if (score >= 70)
Console.WriteLine("Điểm: C");
else
Console.WriteLine("Điểm: F");Switch Expressions (C# 8+)
C# hiện đại thay thế câu lệnh switch dài dòng bằng expression ngắn gọn:
// Câu lệnh switch cũ
string grade;
switch (score / 10)
{
case 10:
case 9: grade = "A"; break;
case 8: grade = "B"; break;
case 7: grade = "C"; break;
default: grade = "F"; break;
}
// Switch expression hiện đại — gọn gàng hơn nhiều
string grade = (score / 10) switch
{
>= 9 => "A",
8 => "B",
7 => "C",
_ => "F" // _ là pattern discard/default
};Pattern Matching
Pattern matching của C# biểu cảm hơn instanceof của Java:
object obj = "Hello, World!";
// Type pattern
if (obj is string text)
Console.WriteLine($"Chuỗi có độ dài {text.Length}");
// Property pattern
if (obj is string { Length: > 5 } longText)
Console.WriteLine($"Chuỗi dài: {longText}");
// Switch với patterns
string description = obj switch
{
int n when n > 0 => "số nguyên dương",
int n when n < 0 => "số nguyên âm",
string s => $"chuỗi: {s}",
null => "null",
_ => "thứ gì đó khác"
};Vòng Lặp
// Vòng lặp for
for (int i = 0; i < 5; i++)
Console.WriteLine(i);
// foreach (phổ biến nhất cho collections)
string[] fruits = ["Apple", "Banana", "Orange"]; // Collection expression C# 12
foreach (var fruit in fruits)
Console.WriteLine(fruit);
// while
int count = 0;
while (count < 5)
{
Console.WriteLine(count);
count++;
}
// do-while
do
{
Console.WriteLine("Chạy ít nhất một lần");
count++;
} while (count < 5);Methods
Methods trong C# luôn là thành viên của một kiểu (class, struct, hoặc record).
Cú Pháp Method Cơ Bản
// File-scoped namespace (C# 10+) — ít indent hơn
namespace LearningCSharp;
public class Calculator
{
// Method có giá trị trả về
public int Add(int a, int b) => a + b; // Expression body
// Void method
public void PrintSum(int a, int b)
{
Console.WriteLine($"Tổng: {a + b}");
}
// Method với nhiều tham số
public double Average(int[] numbers)
{
if (numbers.Length == 0) return 0;
return (double)numbers.Sum() / numbers.Length;
}
}Các Modifier Tham Số
C# có các modifier tham số mạnh mẽ mà Java và Python không có:
public class ParameterExamples
{
// ref — truyền theo tham chiếu (phải được khởi tạo trước)
public void DoubleIt(ref int value)
{
value *= 2;
}
// out — trả về giá trị qua tham số (không cần khởi tạo trước)
public bool TryDivide(int a, int b, out double result)
{
if (b == 0) { result = 0; return false; }
result = (double)a / b;
return true;
}
// in — tham chiếu chỉ đọc (cho structs lớn, tránh copy)
public double GetLength(in Vector3 v) => Math.Sqrt(v.X * v.X + v.Y * v.Y + v.Z * v.Z);
// params — tham số có độ dài thay đổi
public int Sum(params int[] numbers) => numbers.Sum();
}
// Cách dùng
var calc = new ParameterExamples();
int x = 5;
calc.DoubleIt(ref x);
Console.WriteLine(x); // 10
if (calc.TryDivide(10, 3, out double quotient))
Console.WriteLine(quotient); // 3.333...
int total = calc.Sum(1, 2, 3, 4, 5); // 15Pattern TryDivide (trả về bool + tham số out) là C# idiomatico và được dùng trong toàn bộ thư viện chuẩn .NET (ví dụ: int.TryParse()).
Tham Số Tùy Chọn và Named Arguments
public void Greet(string name, string greeting = "Xin chào", bool uppercase = false)
{
var message = $"{greeting}, {name}!";
Console.WriteLine(uppercase ? message.ToUpper() : message);
}
// Cách dùng
Greet("Alice"); // "Xin chào, Alice!"
Greet("Bob", "Chào"); // "Chào, Bob!"
Greet("Carol", uppercase: true); // "XIN CHÀO, CAROL!"
Greet("Dave", greeting: "Hey", uppercase: true); // "HEY, DAVE!"Method Overloading
public class Printer
{
public void Print(int value) => Console.WriteLine($"int: {value}");
public void Print(double value) => Console.WriteLine($"double: {value}");
public void Print(string value) => Console.WriteLine($"string: {value}");
}Strings
Strings trong C# là reference types bất biến — chúng hoạt động như value types trong thực tế.
String Interpolation
string name = "Alice";
int age = 30;
// String interpolation (ưu tiên dùng)
string message = $"Tên: {name}, Tuổi: {age}";
// Định dạng bên trong interpolation
double price = 9.99;
string formatted = $"Giá: {price:C}"; // "Giá: $9.99"
string padded = $"{name,10}"; // " Alice" (căn phải, rộng 10)
string dateStr = $"{DateTime.Now:yyyy-MM-dd}"; // "2026-02-25"Raw String Literals (C# 11+)
Cho chuỗi nhiều dòng không cần escape:
// Raw string literal — không cần escape dấu gạch chéo hay nháy
string json = """
{
"name": "Alice",
"path": "C:\\Users\\Alice"
}
""";
// Interpolated raw string
string sql = $"""
SELECT *
FROM users
WHERE name = '{name}'
AND age > {age}
""";Các String Methods Phổ Biến
string text = " Xin chào, C#! ";
text.ToUpper() // " XIN CHÀO, C#! "
text.ToLower() // " xin chào, c#! "
text.Trim() // "Xin chào, C#!"
text.TrimStart() // "Xin chào, C#! "
text.Contains("C#") // true
text.StartsWith(" Xin") // true
text.Replace("C#", ".NET") // " Xin chào, .NET! "
text.Split(',') // [" Xin chào", " C#! "]
text.Substring(2, 8) // "Xin chào"
// So sánh chuỗi (ordinal nhanh; dùng cho hầu hết trường hợp)
string.Equals("xin chào", "XIN CHÀO", StringComparison.OrdinalIgnoreCase) // true
// Kiểm tra null hoặc rỗng
string.IsNullOrEmpty(text) // false
string.IsNullOrWhiteSpace(" ") // trueStringBuilder cho Nối Chuỗi
// Không hiệu quả: tạo nhiều string object trung gian
string result = "";
for (int i = 0; i < 1000; i++)
result += i + ",";
// Hiệu quả: bộ đệm mutable
var sb = new StringBuilder();
for (int i = 0; i < 1000; i++)
sb.Append(i).Append(',');
string result = sb.ToString();Records (C# 9+)
Records là một trong những tính năng hiện đại hữu ích nhất của C# — container dữ liệu bất biến với so sánh bằng giá trị được tích hợp sẵn:
// Positional record — cú pháp ngắn gọn
public record Person(string Name, int Age);
// Cách dùng
var alice = new Person("Alice", 30);
var bob = new Person("Bob", 25);
var alice2 = alice with { Age = 31 }; // Thay đổi không phá hủy
Console.WriteLine(alice); // Person { Name = Alice, Age = 30 }
Console.WriteLine(alice == new Person("Alice", 30)); // true — so sánh bằng giá trị!
Console.WriteLine(alice2); // Person { Name = Alice, Age = 31 }Records hoàn hảo cho DTOs, API responses và domain value objects. Chúng cung cấp:
- Bất biến theo mặc định
ToString(),Equals(),GetHashCode()và==được triển khai tự động- Hỗ trợ deconstruction
withexpressions cho cập nhật không phá hủy
Classes Cơ Bản và Properties
Auto-Properties
Properties của C# gọn hơn getters/setters của Java:
public class BankAccount
{
// Auto-property với private setter
public string Owner { get; private set; }
// Auto-property — có thể đọc và ghi
public decimal Balance { get; set; }
// Init-only property (C# 9+) — chỉ set trong quá trình khởi tạo
public string AccountNumber { get; init; }
public BankAccount(string owner, string accountNumber)
{
Owner = owner;
AccountNumber = accountNumber;
Balance = 0m;
}
public void Deposit(decimal amount)
{
if (amount <= 0) throw new ArgumentException("Số tiền phải dương.");
Balance += amount;
}
}
// Cách dùng
var account = new BankAccount("Alice", "ACC-001");
account.Deposit(100m);
Console.WriteLine(account.Balance); // 100
// Object initializer syntax
var account2 = new BankAccount("Bob", "ACC-002") { Balance = 500m };Primary Constructors (C# 12+)
// Primary constructor — tham số có sẵn trong toàn bộ class
public class Point(double x, double y)
{
public double X { get; } = x;
public double Y { get; } = y;
public double DistanceTo(Point other)
{
var dx = X - other.X;
var dy = Y - other.Y;
return Math.Sqrt(dx * dx + dy * dy);
}
public override string ToString() => $"({X}, {Y})";
}
var p1 = new Point(0, 0);
var p2 = new Point(3, 4);
Console.WriteLine(p1.DistanceTo(p2)); // 5File-Scoped Namespaces và Global Usings
Hai tính năng C# hiện đại giúp giảm đáng kể boilerplate:
File-Scoped Namespaces (C# 10+)
// Phong cách cũ — mọi thứ đều indent trong namespace block
namespace MyApp.Services
{
public class UserService
{
// ...
}
}
// Phong cách mới — namespace áp dụng cho toàn bộ file, không cần dấu ngoặc
namespace MyApp.Services;
public class UserService
{
// ...
}Global Usings (C# 10+)
Thêm file GlobalUsings.cs vào dự án:
// GlobalUsings.cs
global using System;
global using System.Collections.Generic;
global using System.Linq;
global using System.Text;
global using System.Threading.Tasks;Bây giờ tất cả các file trong dự án đều có các namespace này mà không cần khai báo using rõ ràng.
Lưu ý: Các dự án .NET 6+ đã có implicit global usings được bật mặc định. Bạn có thể xem những gì được bao gồm trong
obj/Debug/net8.0/MyApp.GlobalUsings.g.cs.
Cách .NET Biên Dịch và Chạy Code
Pipeline Biên Dịch
- Trình biên dịch C# (Roslyn) biên dịch file
.csthành IL (Intermediate Language) - CLR (Common Language Runtime) tải IL
- JIT Compiler chuyển đổi IL thành machine code gốc tại runtime
- Trên .NET hiện đại, ReadyToRun (R2R) pre-biên dịch IL để khởi động nhanh hơn
Điều này tương tự mô hình JVM của Java, nhưng AOT compilation của .NET qua dotnet publish -r <rid> --self-contained có thể tạo ra file thực thi gốc không cần runtime dependency.
File Dự Án (.csproj)
File .csproj là XML đơn giản:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project><Nullable>enable</Nullable>— bật nullable reference types (luôn làm điều này)<ImplicitUsings>enable</ImplicitUsings>— bật default global usings
Best Practices cho Giai Đoạn 1
1. Luôn Bật Nullable Reference Types
<!-- Trong .csproj -->
<Nullable>enable</Nullable>Coi cảnh báo của compiler về nullability như lỗi. Xử lý chúng đúng cách — đừng bỏ qua.
2. Dùng Expression Bodies cho Methods Đơn Giản
// Dài dòng
public int Add(int a, int b)
{
return a + b;
}
// Ngắn gọn — ưu tiên cho các one-liner đơn giản
public int Add(int a, int b) => a + b;3. Dùng TryParse Thay Vì Parse
// Không tốt — ném exception với input không hợp lệ
int value = int.Parse(userInput);
// Tốt — trả về false thay vì ném exception
if (int.TryParse(userInput, out int value))
Console.WriteLine($"Hợp lệ: {value}");
else
Console.WriteLine("Input không hợp lệ");4. Ưu Tiên string.IsNullOrWhiteSpace Hơn != null
// Dễ vỡ — bỏ sót chuỗi rỗng và chuỗi chỉ có khoảng trắng
if (name != null) { ... }
// Mạnh mẽ
if (!string.IsNullOrWhiteSpace(name)) { ... }5. Dùng Records cho Data Containers
// Thay vì một class với boilerplate getters/setters/equals:
public record Product(string Name, decimal Price, int StockCount);
// Bất biến, có thể so sánh, có thể in — không cần boilerplateNhững Lỗi Phổ Biến Cần Tránh
❌ Dùng == để So Sánh Chuỗi Với Các Case Khác Nhau
string a = "xin chào";
string b = "XIN CHÀO";
a == b; // false — so sánh phân biệt hoa thường
// ✅ Dùng StringComparison
string.Equals(a, b, StringComparison.OrdinalIgnoreCase); // true❌ Không Xử Lý Nulls Khi Nullable Reference Types Được Bật
string? name = GetName(); // có thể trả về null
Console.WriteLine(name.Length); // Cảnh báo: có thể null dereference
// ✅ Kiểm tra null trước
if (name is not null)
Console.WriteLine(name.Length);
// ✅ Hoặc dùng toán tử null-conditional
Console.WriteLine(name?.Length ?? 0);❌ Nhầm Lẫn Giữa Value Type Copy và Reference Type Reference
// Value type — ngữ nghĩa sao chép
int a = 10;
int b = a;
b = 20;
Console.WriteLine(a); // 10 — không thay đổi
// Reference type — tham chiếu chia sẻ
var list1 = new List<int> { 1, 2, 3 };
var list2 = list1;
list2.Add(4);
Console.WriteLine(list1.Count); // 4 — list1 cũng thay đổi!❌ Dùng float cho Tính Toán Tài Chính
float price = 0.1f + 0.2f;
Console.WriteLine(price); // 0.3 — nhưng thực ra là 0.30000001192...
// ✅ Dùng decimal cho tiền
decimal price = 0.1m + 0.2m;
Console.WriteLine(price); // 0.3 — chính xácBài Tập Thực Hành
Củng cố học tập của bạn với các bài tập sau:
- Bộ Chuyển Đổi Nhiệt Độ: Chuyển đổi Celsius ↔ Fahrenheit sử dụng
record Temperature(double Value, string Unit)và switch expression - FizzBuzz với Pattern Matching: Dùng switch expression với tuple patterns để triển khai FizzBuzz
- Phân Tích Chuỗi: Đếm từ, câu và nguyên âm trong một chuỗi; dùng
StringBuildercho output - Tài Khoản Ngân Hàng Đơn Giản: Triển khai class
BankAccountvới các methodsDeposit,WithdrawvàTransfer; dùngdecimalcho tiền - Thực Hành TryParse: Viết chương trình đọc số nguyên từ console trong vòng lặp, dùng
int.TryParse, và in tổng khi người dùng nhập số không hợp lệ
Tóm Tắt
Bạn đã học được những kiến thức C# cơ bản thiết yếu:
✅ Thiết lập môi trường phát triển .NET với SDK và IDE
✅ Hiểu value types vs reference types và khi nào dùng mỗi loại
✅ Dùng nullable reference types để kiểm tra null an toàn tại thời điểm biên dịch
✅ Viết control flow với switch expressions và pattern matching hiện đại
✅ Định nghĩa methods với ref, out, params và tham số tùy chọn
✅ Làm việc với strings, interpolation, raw string literals và StringBuilder
✅ Tạo classes cơ bản với auto-properties và primary constructors
✅ Dùng records cho các data container bất biến
✅ Áp dụng file-scoped namespaces và global usings để giảm boilerplate
Các Bước Tiếp Theo
Bạn đã sẵn sàng chuyển sang Giai Đoạn 2: Lập Trình Hướng Đối Tượng, nơi bạn sẽ học:
- Kế thừa, method overriding và các từ khóa
virtual/override/sealed - Interfaces, abstract classes và polymorphism
- Encapsulation với access modifiers
- Records nâng cao và
withexpressions - Extension methods và pattern matching chuyên sâu
🚀 Tiếp tục Giai Đoạn 2: Lập Trình Hướng Đối Tượng →
Series: Lộ Trình Học C#
Trước: Lộ Trình Học C# ←
Tiếp theo: Giai Đoạn 2: Lập Trình Hướng Đối Tượng →
Chúc bạn code vui! 💜
📬 Đăng ký nhận bản tin
Nhận những bài viết mới nhất qua email mỗi tuần. Không spam, hủy đăng ký bất cứ lúc nào.
Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất cứ lúc nào.
💬 Comments
Đăng nhập để bình luận
Chúng tôi sẽ không đăng bất cứ điều gì mà không có sự cho phép của bạn.