Nullable Type trong C#

Như bạn đã biết, kiểu giá trị thì không thể được gán một giá trị null. Ví dụ: int i = null sẽ gây ra lỗi khi biên dịch.

C# 2.0 đã giới thiệu các kiểu dữ liệu có thể null (nullable type) cho phép bạn gán null cho các biến kiểu giá trị. Bạn có thể khai báo các nullable type bằng cách sử dụng Nullable<T> với T là một kiểu dữ liệu.

Nullable<int> i = null;

Một kiểu nullable có phạm vi giá trị tương tự như kiểu dữ liệu cơ sở của nó, được bổ sung thêm giá trị null. Ví dụ: Nullable<int> có thể được gán bất kỳ giá trị nào từ -2147483648 đến 2147483647 hoặc giá trị null.

Các kiểu nullable là các thể hiện của struct System.Nullable<T>. Có thể xem nó giống như struct sau đây.

[Serializable]
public struct Nullable<T> where T : struct
{        
    public bool HasValue { get; }
      
    public T Value { get; }
        
    // other implementation
}

Một biến kiểu int nullable về cơ bản cũng giống như một biến kiểu int, chỉ khác ở chỗ biến kiểu int nullable có thể được gán giá trị null.

static void Main(string[] args)
{
    Nullable<int> i = null;

    if (i.HasValue)
    {
        Console.WriteLine(i.Value); // or Console.WriteLine(i)
    }
    else
    {
        Console.WriteLine("Null");
    }
}

Đây là kết quả khi biên dịch và chạy chương trình:

Null

Thuộc tính HasValue trả về true nếu đối tượng đã được gán một giá trị; nếu nó chưa được gán bất kỳ giá trị nào hoặc đã được gán một giá trị null, nó sẽ trả về false .

Truy cập giá trị của biến bằng thuộc tính Value sẽ ném ra một ngoại lệ lúc thực thi nếu biến kiểu nullable là null hoặc không được gán bất kỳ giá trị nào. Ví dụ dưới đây sẽ minh họa điều này:

Khai báo nullable type không hợp lệ trong C#

Để tránh xảy ra ngoại lệ như trên, bạn có thể sử dụng phương thức GetValueOrDefault() để nhận giá trị thực nếu nó không phải là null và giá trị mặc định nếu nó là null. Ví dụ sau minh họa về cách sử dụng phương thức GetValueOrDefault():

static void Main(string[] args)
{
    Nullable<int> i = null;

    Console.WriteLine(i.GetValueOrDefault()); 
}

Cú pháp tốc ký cho kiểu nullable

Bạn có thể dùng toán tử ? để viết tắt cú pháp khai báo biến kiểu nullable. Ví dụ int?, long? thay vì sử dụng Nullable<T>.

int? i = null;
double? d = null;

Toán tử ??

Sử dụng toán tử ?? để trả về một giá trị mặc định nếu biến ở bên trái toán tử ?? có giá trị null.

Ví dụ dưới đây sẽ kiểm tra biến i có bị null không bằng cách sử dụng toán tử ??. Nếu biến i khác null thì sẽ trả về giá trị của i, ngược lại sẽ trả về 0.

int? i = null;
            
int j = i ?? 0;

Console.WriteLine(j);

Đây là kết quả khi biên dịch và chạy chương trình:

0

Quy tắc gán

Một kiểu nullable có các quy tắc gán giống như một kiểu giá trị. Nó phải được gán một giá trị trước khi sử dụng nó nếu các kiểu nullable được khai báo trong một phương thức dưới dạng các biến cục bộ. Nếu nó là một trường của bất kỳ lớp nào thì nó sẽ có giá trị null theo mặc định.

Ví dụ, biến i kiểu int nullable được khai báo và sử dụng mà không gán bất kỳ giá trị nào. Trình biên dịch sẽ đưa ra lỗi Use of unassigned local variable 'i':

Lỗi biến chưa được gán dữ liệu trong C#

Trong ví dụ sau, trường i kiểu int nullable của lớp MyClass, vì vậy nó mặc định sẽ được gán giá trị null và không xảy ra bất kỳ lỗi nào khi biên dịch.

class MyClass
{
    public Nullable<int> i;
}
class Program
{
    static void Main(string[] args)
    {
        MyClass myClass = new MyClass();

        if (myClass.i == null)
        {
            Console.WriteLine("Null");
        }
    }
}

Đây là kết quả khi biên dịch và chạy chương trình:

Null

Lớp trợ giúp nullable

Null đồng nghĩa với không có bất kỳ giá trị gì. Vì vậy, các toán tử so sánh sẽ không hoạt động với null. Ví dụ sau sẽ minh họa điều này:

static void Main(string[] args)
{
    int? i = null;
    int j = 10;


    if (i < j)
    {
        Console.WriteLine("i < j");
    }
    else if ( i > 10)
    {
        Console.WriteLine("i > j");
    }
    else if ( i == 10)
    {
        Console.WriteLine("i == j");
    }
    else
    {
        Console.WriteLine("Could not compare");
    }
}

Đây là kết quả khi biên dịch và chạy chương trình:

Could not compare

Lớp Nullable là lớp tĩnh trợ giúp cho các kiểu nullable. Nó cung cấp một phương thức so sánh để so sánh các kiểu nullable. Nó cũng có một phương thức GetUnderlyingType trả về kiểu cơ bản của kiểu nullable.

static void Main(string[] args)
{
    int? i = null;
    int j = 10;

    if (Nullable.Compare<int>(i, j) < 0)
    {
        Console.WriteLine("i < j");
    }
    else if (Nullable.Compare<int>(i, j) > 0)
    {
        Console.WriteLine("i > j");
    }
    else
    {
        Console.WriteLine("i = j");
    }
} 

Đây là kết quả khi biên dịch và chạy chương trình:

i < j

Đặc điểm của kiểu nullable

  • Kiểu nullable chỉ có thể được sử dụng với các kiểu giá trị.
  • Thuộc tính Value sẽ ném ra một ngoại lệ UnlimitedOperationException nếu giá trị là null; ngược lại nó sẽ trả về giá trị.
  • Thuộc tính HasValue trả về true nếu biến chứa giá trị hoặc false nếu nó là null.
  • Bạn chỉ có thể sử dụng các toán tử ==!= với kiểu nullable. Để thực hiện các so sánh khác, bạn phải sử dụng lớp tĩnh Nullable.
  • Các kiểu nullable lồng nhau không được phép. Nullable<Nullable<int>> i; sẽ gây ra một lỗi lúc biên dịch.

Những điểm cần nhớ:

  • Nullable<T> cho phép gán giá trị null cho các kiểu giá trị.
  • Toán tử ? là một cú pháp tốc ký cho các kiểu nullable.
  • Sử dụng thuộc tính Value để lấy giá trị của loại nullable, tuy nhiên thuộc tính Value sẽ ném ra một ngoại lệ UnlimitedOperationException nếu giá trị là null.
  • Sử dụng thuộc tính HasValue để kiểm tra xem kiểu nullable có được gán giá trị hay không.
  • Lớp tĩnh Nullable là lớp trợ giúp để so sánh các kiểu nullable.

Kiểu tham chiếu nullable

Bạn không nghe lầm đâu, trước đây chỉ có kiểu giá trị nullable, nhưng từ phiên bản C# 8.0 mới được Microsoft phát hành kèm theo .NET Framework 4.8, bạn sẽ có thêm kiểu tham chiếu nullable.

C# 8.0 giới thiệu các kiểu tham chiếu nullablecác kiểu tham chiếu không nullable cho phép bạn đưa ra các khai báo quan trọng về các thuộc tính cho các biến kiểu tham chiếu:

Một tham chiếu không được coi là null: Khi các biến không được coi là null, trình biên dịch sẽ thực thi các quy tắc đảm bảo an toàn để hủy đăng ký tới các biến này mà không cần kiểm tra trước rằng nó không null:

  • Biến phải được khởi tạo với giá trị khác null.
  • Biến không bao giờ có thể được gán giá trị null.

Một tham chiếu có thể là null: Khi các biến có thể là null, trình biên dịch sẽ thực thi các quy tắc khác nhau để đảm bảo rằng bạn đã kiểm tra chính xác tham chiếu null:

  • Biến chỉ có thể được hủy đăng ký khi trình biên dịch có thể đảm bảo rằng giá trị không null.
  • Các biến này có thể được khởi tạo với giá trị null mặc định và có thể được gán giá trị null trong mã.

Tính năng mới này cung cấp các lợi ích đáng kể trong việc xử lý các biến tham chiếu trong các phiên bản trước của C# trong đó mục đích thiết kế không thể được xác định lúc khai báo biến.

Trình biên dịch không cung cấp các xử lý an toàn trước các ngoại lệ tham chiếu null cho các kiểu tham chiếu:

  • Một tham chiếu có thể là null: Không có cảnh báo nào được đưa ra khi một kiểu tham chiếu được khởi tạo thành null hoặc sau đó nó được gán null.
  • Một tham chiếu được coi là không null: Trình biên dịch không đưa ra bất kỳ cảnh báo nào khi các kiểu tham chiếu bị hủy đăng ký. (Với các tham chiếu nullable, trình biên dịch sẽ đưa ra cảnh báo bất cứ khi nào bạn hủy đăng ký một biến có thể là null).

Với việc bổ sung các kiểu tham chiếu nullable, bạn có thể khai báo ý định thiết kế của mình rõ ràng hơn. Các giá trị null là cách chính xác để đại diện cho rằng một biến không đề cập đến một giá trị.

Không sử dụng tính năng này để xóa tất cả các giá trị null khỏi mã của bạn. Thay vào đó, bạn nên khai báo ý định của mình với trình biên dịch và các nhà phát triển khác đọc mã của bạn.

Bằng cách khai báo ý định của bạn, trình biên dịch sẽ thông báo cho bạn khi bạn viết mã không phù hợp với ý định đó.

Một kiểu tham chiếu nullable được khai báo bằng cách sử dụng cú pháp giống như kiểu giá trị nullable: sử dụng ký tự ? ngay sau kiểu dữ liệu của biến. Ví dụ: khai báo biến dưới đây minh họa biến name kiểu string nullable:

string? name;


Bài viết liên quan:

Hướng dẫn này sẽ giúp bạn tìm hiểu về đọc ghi file (File I/O) trong C# và sử dụng các lớp tiện ích để đọc ghi file.

Reflection trong C#

  • 6 min read

Reflection trong C# là gì? Ứng dụng của Reflection trong C#. Cách khai báo và sử dụng Reflection trong C#.

Attribute trong C#

  • 7 min read

Attribute trong C# là gì? Có những loại attribute nào trong C#? Làm sao để sử dụng attribute trong C#.