Giới thiệu về C# 14

C# 14 chính thức được phát hành cùng với .NET 10. Điểm nhấn chính là các thành viên mở rộng (extension members) mới, nhưng còn rất nhiều tính năng khác giúp cuộc sống của bạn với tư cách là một lập trình viên trở nên hiệu quả hơn. Ngoài ra, chúng tôi cũng đã bổ sung các tính năng mới giúp cải thiện hiệu suất mà bạn có thể trải nghiệm trong .NET 10. Hãy cùng khám phá tất cả các tính năng mới qua bài viết dưới đây.

Thành viên mở rộng (Extension members)

Extension members là tính năng nổi bật nhất của C# 14. Cú pháp mới hoàn toàn tương thích với các phương thức mở rộng hiện có. Extension members cho phép tạo extension properties, extension operators và static extension members.

Đoạn mã sau đây minh họa một khối mở rộng (extension block). Khối mở rộng chứa hai instance extensions, tiếp theo là hai static extensions cho cùng một kiểu dữ liệu. Tên receiver source là tùy chọn nếu extension chỉ chứa static extensions.

public static class EnumerableExtensions
{
    // Instance-style extension members: 'source' là biến receiver
    extension<TSource>(IEnumerable<TSource> source)
    {
        // Extension property
        public bool IsEmpty => !source.Any();

        // Extension method
        public IEnumerable<TSource> Where(Func<TSource, bool> predicate)
        {
            throw new NotImplementedException();
        }

        // Static extension property
        public static IEnumerable<TSource> Identity => Enumerable.Empty<TSource>();

        // Static user-defined operator dưới dạng extension
        public static IEnumerable<TSource> operator +(
            IEnumerable<TSource> left,
            IEnumerable<TSource> right) => left.Concat(right);
    }
}

Ví dụ sử dụng:

int[] data = ...;
// Truy cập instance extension property:
if (data.IsEmpty) { /* ... */ }

// Truy cập static extension operator +
var combined = data + [ 4, 5 ];

// Truy cập static extension property:
var empty = IEnumerable<int>.Identity;

Vì các khối extension tương thích hoàn toàn với các phương thức mở rộng hiện có, bạn có thể di chuyển từng phương thức một mà không cần biên dịch lại các assembly phụ thuộc.

Năng suất làm việc cao hơn

Nhóm tính năng ngôn ngữ này có chung một mục tiêu: giảm thiểu sự phức tạp về mặt cú pháp cho các tác vụ hàng ngày để bạn có thể tập trung vào logic nghiệp vụ thay vì các thủ tục rườm rà. Chúng loại bỏ mã soạn sẵn (boilerplate), loại bỏ các khối điều kiện phổ biến, đơn giản hóa khai báo lambda, cải thiện kiểu partial cho source generators và làm cho nameof trở nên linh hoạt hơn trong các tình huống generic.

Từ khóa field

Hầu hết các property bắt đầu cuộc sống dưới dạng auto-implemented properties. Sau đó, bạn phát hiện ra cần thêm một chút logic — kiểm tra null, chuẩn hóa dữ liệu hoặc kiểm tra điều kiện — trên một accessor. Trước C# 14, điều này buộc bạn phải chuyển đổi sang mẫu khai báo trường nền (backing field) thủ công hoàn chỉnh:

// Trước đây
private string _message = "";
public string Message
{
    get => _message;
    init => _message = value 
              ?? throw new ArgumentNullException(nameof(value));
}

Từ khóa ngữ cảnh field tạo ra một bước trung gian trong quá trình tiến hóa đó: giữ được sự ngắn gọn của auto-property, chỉ thêm logic tối thiểu ở những nơi cần thiết và để trình biên dịch tự động tổng hợp và đặt tên cho bộ nhớ lưu trữ nền:

// Sau (C# 14)
public string Message
{
    get; // auto get
    init => field = value 
              ?? throw new ArgumentNullException(nameof(value));
}

Tính năng này đã có sẵn dưới dạng preview trong .NET 9 và nay đã chính thức có mặt trong C# 14.

Kiểu generic không ràng buộc và nameof

Trước đây, để ghi log hoặc ném ngoại lệ chỉ với tên kiểu generic, bạn phải hardcode một chuỗi hoặc sử dụng một kiểu đã được cung cấp tham số:

// Trước đây
var listTypeName = nameof(List<int>); // "List"

Giờ đây, nameof chấp nhận một kiểu generic không ràng buộc (unbound generic type). Tính năng này loại bỏ nhu cầu phải chọn một tham số kiểu tùy ý chỉ để lấy tên của kiểu generic:

// Sau (C# 14)
var listTypeName = nameof(List<>); // "List"

Tham số lambda đơn giản với modifiers

Trong các phiên bản trước, các modifier tham số như out trong delegate yêu cầu chú thích kiểu đầy đủ trên tất cả các tham số:

// Trước đây
delegate bool TryParse<T>(string text, out T value);
TryParse<int> parse = (string text, out int result) => int.TryParse(text, out result);

Giờ đây, bạn có thể giữ dạng ngắn gọn với kiểu ngầm định trong khi vẫn sử dụng các modifier như out, ref, in, scoped trên một hoặc nhiều tham số:

// Sau (C# 14)
TryParse<int> parse = (text, out result) => int.TryParse(text, out result);

Null-conditional assignment

Trước đây, các phép gán có kiểm tra null yêu cầu kiểm tra tường minh:

// Trước đây
if (customer is not null)
{
    customer.Order = CreateOrder();
    customer.Total += CalculateIncrement();
}

Giờ đây, bạn có thể gán trực tiếp với các toán tử null-conditional ở vế trái của phép gán. Vế phải chỉ được đánh giá khi receiver của phép gán không phải là null:

// Sau (C# 14)
customer?.Order = CreateOrder();
customer?.Total += CalculateIncrement();

Partial events và constructors

Các kiểu partial lớn do nguồn tạo hoặc do con người viết giờ đây có thể trải rộng logic event và constructor trên nhiều tệp:

public partial class Widget(int size, string name)
{
    public partial event EventHandler Changed;
}

public partial class Widget
{
    public partial event EventHandler Changed
    {
        add => _changed += value;
        remove => _changed -= value;
    }

    private EventHandler? _changed;

    public Widget
    {
        Initialize();
    }
}

Hiệu suất tốt hơn cho người dùng

Nhiều cải tiến về thông lượng xử lý mà bạn sẽ thấy sau khi nâng cấp lên .NET 10 đến từ runtime và BCL đã áp dụng các khả năng mới của C# 14. Các thư viện lõi đã sử dụng các tính năng này, vì vậy ứng dụng của bạn thường chạy nhanh hơn ngay cả khi bạn không tự viết cú pháp này. Hai bổ sung ngôn ngữ đặc biệt giúp mở khóa các triển khai thư viện sạch hơn và nhanh hơn: implicit span conversions và user-defined compound assignment.

Implicit span conversions

Span<T> / ReadOnlySpan<T> là trung tâm của các API không cấp phát bộ nhớ. C# 14 thêm các chuyển đổi ngầm định giữa các mảng, span và read-only span để bạn viết ít mã hơn và JIT thấy được các đồ thị gọi đơn giản hơn.

Các phiên bản C# trước yêu cầu mã như sau:

// Trước đây
string line = ReadLine();
ReadOnlySpan<char> key = line.AsSpan(0, 5);
ProcessKey(key);

int[] buffer = GetBuffer();
Span<int> head = new(buffer, 0, 8);
Accumulate(head);

Giờ đây, bạn có thể viết:

// Sau (C# 14)
string line = ReadLine();
ProcessKey(line[..5]);

int[] buffer = GetBuffer();
Accumulate(buffer[..8]);

User defined compound assignment

Các kiểu số và vector hiệu suất cao thường tích lũy giá trị trong các vòng lặp chặt. C# 14 cho phép bạn khai báo toán tử gán kết hợp (+=, -=, v.v.) một cách tường minh để trình biên dịch gọi trực tiếp đến triển khai của bạn.

Thay vì:

// Trước đây
BigVector sum = BigVector.Zero;
foreach (var v in values)
{
    sum = sum.Add(v);
}

Sau khi bạn cung cấp một compound operator có thể cập nhật kết quả tại chỗ:

// Sau (C# 14)
BigVector sum = BigVector.Zero;
foreach (var v in values)
{
    sum += v; // gọi user-defined operator += trực tiếp
}

Định nghĩa cả binary operator và compound operator:

public struct BigVector(float x, float y, float z)
{
    public float X { get; private set => value = field; } = x;
    public float Y { get; private set => value = field; } = y;
    public float Z { get; private set => value = field; } = z;

    public static BigVector operator +(BigVector l, BigVector r)
        => new(l.X + r.X, l.Y + r.Y, l.Z + r.Z);

    public void operator +=(BigVector r)
    {
        X += r.X;
        Y += r.Y;
        Z += r.Z;
    }
}

Tổng kết

Trên đây là tổng quan nhanh về những gì chúng tôi đã mang đến trong C# 14: extension members mới, nhiều tính năng giúp bạn làm việc hiệu quả hơn và các cải tiến giúp chương trình C# của bạn chạy tốt hơn. Hãy tải .NET 10 và dùng thử trên ứng dụng của bạn ngay hôm nay!

Chỉ mục