CodeGym /Các khóa học /C# SELF /Làm việc với kiểu: is...

Làm việc với kiểu: is, as và Pattern Matching

C# SELF
Mức độ , Bài học
Có sẵn

1. Toán tử is: kiểm tra tương thích kiểu

Trong ứng dụng thực tế, nhiều khi bạn có một tham chiếu đến object kiểu cha, nhưng bạn biết hoặc nghi ngờ rằng object thực tế trong bộ nhớ là một kiểu con cụ thể hơn. Lúc này, bạn có thể cần truy cập vào các thành viên riêng của kiểu con đó. Để làm việc an toàn và tiện lợi với tình huống này, C# có mấy công cụ chính: toán tử is, as và, đặc biệt là trong các phiên bản mới, các tính năng mạnh mẽ của pattern matching.

is cho phép kiểm tra xem object có phải là instance của kiểu chỉ định, kiểu con của nó hoặc có implement interface nào đó không. Nó trả về true nếu object tương thích với kiểu đó, và false nếu không.

Ứng dụng chính: xác định xem có thể chuyển object sang kiểu nào đó một cách an toàn không.

Ví dụ: Kiểm tra kiểu cha và kế thừa


class Animal { public string Species { get; set; } = "Không xác định"; }
class Dog : Animal { public void Bark() => Console.WriteLine("Gâu!"); }
class Cat : Animal { public void Meow() => Console.WriteLine("Meo!"); }

Animal myAnimal = new Dog { Species = "Golden Retriever" };

Console.WriteLine($"myAnimal is Animal: {myAnimal is Animal}"); // True
Console.WriteLine($"myAnimal is Dog: {myAnimal is Dog}");       // True
Console.WriteLine($"myAnimal is Cat: {myAnimal is Cat}");       // False

Trong ví dụ này myAnimal thực chất là Dog. Vì vậy myAnimal is AnimalmyAnimal is Dog sẽ trả về true.

Ví dụ: Kiểm tra null

Toán tử is cũng hoạt động dự đoán được với null.


Animal nullAnimal = null;
Dog specificDog = new Dog();

Console.WriteLine($"nullAnimal is Animal: {nullAnimal is Animal}"); // False (null không phải là instance của bất kỳ kiểu nào)
Console.WriteLine($"specificDog is null: {specificDog is null}");   // False (object không phải null)

Lưu ý rằng null không phải là instance của bất kỳ kiểu nào, nên null is MyType luôn false. Tuy nhiên someObject is null là cách tiện và an toàn để kiểm tra xem tham chiếu có phải null không.

Ví dụ: Dùng is với interface

Toán tử is cũng dùng để kiểm tra implement interface.


interface IFlyable { void Fly(); }
class Bird : Animal, IFlyable { public void Fly() => Console.WriteLine("Phạch phạch!"); }
class Fish : Animal { }

Animal creature = new Bird();

Console.WriteLine($"creature is Bird: {creature is Bird}");     // True
Console.WriteLine($"creature is IFlyable: {creature is IFlyable}"); // True

creature = new Fish();
Console.WriteLine($"creature is IFlyable: {creature is IFlyable}"); // False

2. Toán tử as: chuyển kiểu an toàn

Toán tử as dùng để chuyển object sang kiểu chỉ định một cách an toàn. Khác với ép kiểu tường minh (Type)obj sẽ ném InvalidCastException nếu thất bại, as trả về null nếu không chuyển được. Rất hợp khi bạn không chắc object thực sự là kiểu gì.

Ứng dụng chính: Thử chuyển kiểu, nhận null nếu thất bại.

Ví dụ: Dùng cơ bản as


class Shape { }
class Circle : Shape { public double Radius { get; set; } }
class Square : Shape { public double Side { get; set; } }

Shape myShape = new Circle { Radius = 5.0 };

// Thử chuyển sang Circle
Circle circle = myShape as Circle;
if (circle != null) // Nhớ kiểm tra null!
{
    Console.WriteLine($"Đây là hình tròn bán kính: {circle.Radius}"); // Output: Đây là hình tròn bán kính: 5
}

// Thử chuyển sang Square
Square square = myShape as Square;
if (square == null) // Không chuyển được, square == null
{
    Console.WriteLine("Đây không phải hình vuông."); // Output: Đây không phải hình vuông.
}

Như bạn thấy, as giúp tránh lỗi runtime, trả về null thay vì exception.

Ví dụ: Giới hạn của as

Lưu ý, toán tử as chỉ dùng cho reference type và nullable value type. Không dùng được với value type thường vì chúng không nhận giá trị null.


object someValue = 100;

int num = someValue as int; // LỖI BIÊN DỊCH: 'as' không dùng cho kiểu không null
int? nullableNum = someValue as int?; // Ok, nullable int
Console.WriteLine($"Nullable int: {nullableNum}"); // Output: Nullable int: 100

string str = someValue as string; // Trả về null vì 100 không phải string
Console.WriteLine($"String from int: {str ?? "null"}"); // Output: String from int: null

Với value type không nullable, hãy dùng ép kiểu tường minh ((int)someValue) nếu chắc chắn về kiểu (và chấp nhận exception), hoặc tốt nhất là dùng pattern matching.

3. Pattern Matching (So khớp mẫu)

Pattern matching là tính năng mạnh mẽ và ngày càng phát triển, cung cấp cách kiểm tra kiểu và lấy dữ liệu từ object gọn gàng, an toàn hơn. Nó giúp giảm code lặp và dễ đọc hơn nhiều.

Type Pattern (Mẫu kiểu) với is

Đây là dạng pattern matching phổ biến nhất, cho phép kiểm tra kiểu object và nếu đúng thì gán luôn cho biến kiểu đó.

Cú pháp: biểu_thức is Kiểu biến

Ví dụ: Thay thế is + ép kiểu


// Tiếp tục với class Shape, Circle, Square

Shape currentShape = new Circle { Radius = 7.5 };

// Cách cũ, dài dòng:
if (currentShape is Circle)
{
    Circle c = (Circle)currentShape;
    Console.WriteLine($"Cách cũ: Hình tròn bán kính {c.Radius}");
}

// Cách mới, gọn với type pattern
if (currentShape is Circle c) // Kiểm tra kiểu và tạo biến 'c'
{
    Console.WriteLine($"Cách mới: Hình tròn bán kính {c.Radius}"); // 'c' đã là kiểu Circle
}

Shape anotherShape = new Square { Side = 10.0 };
if (anotherShape is Square s)
{
    Console.WriteLine($"Đây là hình vuông cạnh {s.Side}");
}

Biến c (hoặc s) chỉ dùng được trong block if, tránh dùng nhầm ngoài context đúng kiểu.

Property Pattern (Mẫu thuộc tính)

Từ C# 8.0, bạn không chỉ kiểm tra kiểu mà còn kiểm tra giá trị một hoặc nhiều thuộc tính của object, đồng thời lấy ra biến mới.

Cú pháp: biểu_thức is Kiểu { ThuộcTính1: mẫu_giá_trị, ThuộcTính2: biến_lấy_ra }

Ví dụ: Kiểm tra thuộc tính


Shape testShape = new Circle { Color = "Xanh lá", Radius = 12.0 };

// Kiểm tra object là hình tròn và màu xanh lá
if (testShape is  Circle { Color: "Xanh lá" })
{
    Console.WriteLine("Tìm thấy hình tròn màu xanh lá.");
}

// Kiểm tra object là hình tròn, đồng thời lấy bán kính
if (testShape is  Circle { Radius: var r })
{
    Console.WriteLine($"Bán kính lấy ra: {r}");
}

// Kết hợp kiểm tra và lấy giá trị:
if (testShape is  Circle { Color: "Xanh lá", Radius: var radiusVal } circleObj)
{
    Console.WriteLine($"Lấy được hình tròn xanh lá. Bán kính: {radiusVal}, Object: {circleObj.Radius}");
}

Mẫu thuộc tính giúp đơn giản hóa các điều kiện phức tạp.

Ví dụ: Khoảng và toán tử logic trong Property Patterns

Giờ bạn có thể kiểm tra thuộc tính object thỏa mãn điều kiện nào đó:


Circle bigCircle = new Circle { Radius = 25.0, Color = "Xanh dương" };

// Kiểm tra bán kính > 20 VÀ màu xanh dương
if (bigCircle is Circle { Radius: > 20, Color: "Xanh dương" })
{
    Console.WriteLine("Phát hiện hình tròn xanh dương lớn.");
}

// Kiểm tra bán kính trong khoảng (5..15)
if (testShape is Circle { Radius: >= 5 and <= 15 })
{
    Console.WriteLine("Hình tròn cỡ vừa.");
}

Lưu ý! Khác với kiểu bool, ở đây dùng từ khóa: and, ornot.

4. Switch Expressions và Switch Statements với Pattern Matching

Pattern matching phát huy tối đa sức mạnh và dễ đọc khi dùng trong switch. Bạn có thể thực hiện logic khác nhau tùy theo mẫu khớp.

Ví dụ: Switch Statement

Cho phép xác định block code cho từng mẫu khớp.


// Class Animal, Dog, Cat như đầu bài
Animal currentCreature = new Dog { Species = "Poodle" };

switch (currentCreature)
{
    case Dog d: // Mẫu kiểu: nếu là Dog, gán cho biến 'd'
        d.Bark();
        Console.WriteLine($"Đây là chó giống {d.Species}.");
        break;
    case Cat c when c.Species == "Siamese": // Mẫu kiểu với điều kiện (when clause)
        c.Meow();
        Console.WriteLine($"Đây là mèo Xiêm.");
        break;
    case Animal a: // Nếu chỉ là Animal (không phải Dog/Cat)
        Console.WriteLine($"Chỉ là động vật loại {a.Species}.");
        break;
    case null: // Mẫu null để xử lý giá trị null
        Console.WriteLine("Object bằng null.");
        break;
    default: // Nếu không khớp mẫu nào ở trên
        Console.WriteLine("Sinh vật không xác định.");
        break;
}

Thứ tự case trong switch quan trọng: mẫu cụ thể phải đặt trước mẫu tổng quát.

Ví dụ: Switch Expression

Đây là cú pháp gọn hơn cho switch, trả về giá trị. Rất hợp để chuyển object thành giá trị khác dựa vào kiểu hoặc thuộc tính.

Cú pháp: biểu_thức switch { mẫu1 => kết_quả1, mẫu2 => kết_quả2, ... _ => kết_quả_mặc_định }


Shape processShape = new Rectangle { Width = 5, Height = 5, Color = "Đỏ" };

string shapeInfo = processShape switch
{
    Circle { Radius: var r } when r > 10 => $"Hình tròn lớn (R={r})", // Mẫu thuộc tính với điều kiện
    Circle { Color: "Xanh dương" } c =>  $"Hình tròn xanh dương (R={c.Radius})",   // Mẫu thuộc tính lấy giá trị
    Circle c =>  $"Hình tròn thường (R={c.Radius})",                     // Mẫu kiểu đơn giản
    Rectangle { Width: var w, Height: var h } when w == h =>  $"Hình vuông ({w}x{h})", // Hình vuông
    Rectangle r =>  $"Hình chữ nhật ({r.Width}x{r.Height})",         // Hình chữ nhật khác
    null =>  "Không có hình (null)",                           // Mẫu null
    _ =>  "Hình không xác định"                                     // Mẫu discard (_), thay cho 'default'
};

Console.WriteLine(shapeInfo); // Output: Hình vuông (5x5)

switch expression rất tiện cho các chuyển đổi ngắn gọn, dễ hiểu.

Var Pattern (Mẫu var)

Từ C# 7.0, bạn có thể dùng var trong pattern matching. var luôn khớp với mọi object (trừ null) và lấy ra biến đúng kiểu.

Ví dụ: Dùng var Pattern


object obj = "Hello World";

if (obj is var  result) // Luôn true, result sẽ là string
{
    Console.WriteLine($"Kiểu: {result.GetType().Name}, Giá trị: {result}");
}

obj = 123;
string typeName = obj switch
{
    var x when x is int => "Đây là số nguyên",
    var y when y is string =>  "Đây là chuỗi",
    _ => "Cái gì khác"
};
Console.WriteLine(typeName); // Output: Đây là số nguyên

var Pattern ít khi dùng riêng để kiểm tra kiểu, nhưng rất hữu ích trong các mẫu khác hoặc switch để lấy giá trị.

5. is vs as vs Pattern Matching: dùng khi nào?

Chọn công cụ nào tùy vào mục đích và phiên bản C# bạn dùng.

Toán tử is (đơn giản):

Dùng khi: Bạn chỉ cần kiểm tra object có phải kiểu nào đó không, và không cần ép kiểu hay truy cập thành viên riêng ngay.


    if (myObject is SomeType)

Toán tử as:

Dùng khi: Bạn muốn chuyển reference type (hoặc nullable value type) và muốn xử lý thất bại mà không bị exception, bằng cách kiểm tra null.


    MyDerivedClass derived = myBaseObject as MyDerivedClass;
if (derived != null) { /* ... */ }

Pattern Matching (is Type variable):

Cách hiện đại nên dùng: Kết hợp kiểm tra kiểu và ép kiểu an toàn trong một dòng, dễ đọc. Dùng được cho mọi kiểu.

Dùng khi: Bạn muốn kiểm tra kiểu và dùng luôn thành viên riêng của kiểu đó.


    if (myObject is MyDerivedClass derived) { derived.SpecificMethod(); }

Pattern Matching (switch expressions / statements):

Cách hiện đại nên dùng: Khi bạn cần thực hiện hành động khác nhau hoặc lấy giá trị khác nhau tùy kiểu object, thuộc tính hoặc đặc điểm khác. Gọn hơn nhiều so với chuỗi if-else if dài.

Dùng khi: Triển khai hành vi đa hình hoặc logic chọn phức tạp dựa trên đặc điểm object.


string GetShapeInfo(Shape s) => s switch
{
    Circle c => $"Hình tròn R={c.Radius}",
    Rectangle r => $"Hình chữ nhật W={r.Width} H={r.Height}",
    _ => "Không xác định"
};
2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Kiểm tra kiểu đối tượng bằng toán tử `is`
Kiểm tra kiểu đối tượng bằng toán tử `is`
1
Khảo sát/đố vui
, cấp độ , bài học
Không có sẵn
Thực thi trì hoãn
Tối ưu hóa làm việc với collection
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION