Tính đa hình trong C#

Tính đa hình (Polymorphism) là một trong 4 tính chất trụ cột của lập trình hướng đối tượng OOP.

  • Tính đóng gói (Encapsulation).
  • Tính kế thừa (Inheritance).
  • Tính đa hình (Polymorphism).
  • Tính trừu tượng (Abstraction).

Nếu chưa hiểu rõ về lập trình hướng đối tượng thì bạn nên tham khảo bài viết dưới đây trước:

Lập trình hướng đối tượng (OOP) trong C# | Comdy
Lập trình hướng đối tượng (OOP) trong C# là gì? 5 khái niệm và 4 tính chất quan trọng của lập trình hướng đối tượng là gì?

Tính đa hình là khả năng một đối tượng có thể thực hiện một tác vụ theo nhiều cách khác nhau. Đa hình gồm 2 loại là đa hình tĩnh và đa hình động.

Đa hình tĩnh (overloading)

Đa hình tĩnh còn gọi là đa hình tại thời điểm biên dịch (compile time). C# cung cấp hai kỹ thuật để thực hiện đa hình tĩnh là:

  • Nạp chồng phương thức: là các phương thức có cùng tên nhưng khác nhau về số lượng và/hoặc kiểu dữ liệu của tham số truyền vào.
  • Nạp chồng toán tử: Bạn có thể định nghĩa lại hoặc nạp chồng hầu hết các toán tử tích hợp sẵn có trong C#. Nhờ đó, bạn có thể sử dụng các toán tử với các kiểu do người dùng định nghĩa.

Nạp chồng phương thức

Bạn có thể thực hiện nạp chồng phương thức bằng cách tạo nhiều phương thức cùng tên nhưng khác nhau về kiểu dữ liệu và/hoặc số lượng đối số trong danh sách đối số. Bạn không thể nạp chồng phương thức khi phương thức chỉ khác nhau về kiểu trả về.

Ví dụ sau đây cho thấy sử dụng phương thức Resize() để thay đổi kích thước chiều dài và rộng của hình chữ nhật:

using System;

namespace BoxApplication 
{
   class Rectangle 
   {
      public double Width { get; set; }
      public double Height { get; set; }
      
      public Rectangle(double width, double height)
      {
      	  Width = width;
          Height = height;
      }
      
      // overload method
      public void Resize(double sameSize)
      {
      	  Width = sameSize;
          Height = sameSize;
      }      
      public void Resize(int width, int height)
      {
      	  Width = width;
          Height = height;
      }      
      public void Resize(double width, double height)
      {
      	  Width = width;
          Height = height;
      }
            
      public double GetArea()
      {
      	  return Width * Height;
      }
   }
   
   class RectangleTester 
   {
      static void Main(string[] args) 
      {
         Rectangle rectangle1 = new Rectangle(6.0, 7.0);
         Console.WriteLine("Area = {0} x {1} = {2}", rectangle1.Width, rectangle1.Height, rectangle1.GetArea());
         
         rectangle1.Resize(5.0);
         Console.WriteLine("Area = {0} x {1} = {2}", rectangle1.Width, rectangle1.Height, rectangle1.GetArea());
                
         Console.ReadKey();
      }
   }
}

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

Area = 6 x 7 = 42
Area = 5 x 5 = 25

Nạp chồng toán tử

Bạn có thể định nghĩa lại hoặc nạp chồng hầu hết các toán tử tích hợp sẵn có trong C#.

Do đó, một lập trình viên cũng có thể sử dụng các toán tử với các kiểu do người dùng định nghĩa.

Nạp chồng toán tử là các phương thức có tên đặc biệt với từ khóa operator và theo sau là ký hiệu cho toán tử được chỉ định.

Tương tự như bất kỳ phương thức nào khác, nạp chồng toán tử có kiểu trả về và danh sách đối số.

Ví dụ: đi qua chức năng sau:

public static Box operator +(Box b, Box c) 
{
   Box box = new Box();
   box.length = b.length + c.length;
   box.breadth = b.breadth + c.breadth;
   box.height = b.height + c.height;
   return box;
}

Hàm trên thực hiện nạp chồng toán tử cộng (+) cho lớp Box do người dùng định nghĩa. Nó cộng các thuộc tính của hai đối tượng Box và trả về đối tượng Box kết quả.

Thực hiện nạp chồng toán tử

Chương trình sau đây cho thấy việc thực hiện đầy đủ:

using System;

namespace BoxApplication 
{
   class Rectangle 
   {
      public double Width { get; set; }
      public double Height { get; set; }
      
      public Rectangle(double width, double height)
      {
      	  Width = width;
          Height = height;
      }      
            
      // overload operator
      public static Rectangle operator +(Rectangle a, Rectangle b) 
      {
         return new Rectangle(a.Width + b.Width, a.Height + b.Height);
      }
      public static Rectangle operator -(Rectangle a, Rectangle b) 
      {
         return new Rectangle(a.Width - b.Width, a.Height - b.Height);
      }
            
      // other methods
      public double GetArea()
      {
      	  return Width * Height;
      }
   }
   
   class RectangleTester 
   {
      static void Main(string[] args) 
      {
         Rectangle rectangle1 = new Rectangle(3.0, 5.0);         
         Rectangle rectangle2 = new Rectangle(2.0, 4.0);
         
         // use operator +
         Rectangle rectangle3 = rectangle1 + rectangle2;
         Console.WriteLine("Area = {0} x {1} = {2}", rectangle3.Width, rectangle3.Height, rectangle3.GetArea());
                
         Console.ReadKey();
      }
   }
}

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

Area = 5 x 9 = 45

Toán tử quá tải và không quá tải

Bảng sau mô tả khả năng nạp chồng của các toán tử trong C#:

STT Toán tử & Mô tả
1 +, -,!, ~, ++, -
Các toán tử đơn nguyên này có một toán hạng và có thể nạp chồng.
2 +, -, *, /,%
Các toán tử nhị phân này có một toán hạng và có thể nạp chồng.
3 == ,! =, <,>, <=,> =
Các toán tử so sánh có thể nạp chồng.
4 &&, ||
Các toán tử logic có điều kiện không thể nạp chồng trực tiếp.
5 + =, - =, * =, / =,% =
Các toán tử gán không thể nạp chồng.
6 =,.,?:, ->, new, is, sizeof, typeof
Các toán tử không thể nạp chồng.

Đa hình động (overriding)

Đa hình động là đa hình lúc thực thi (runtime). C# cho phép bạn tạo các lớp trừu tượng (abstract class) được sử dụng để cung cấp triển khai lớp một phần của giao diện.

Việc thực hiện được hoàn thành khi một lớp dẫn xuất kế thừa từ nó. Các lớp trừu tượng chứa các phương thức trừu tượng (abstract method) sẽ được thực hiện bởi lớp dẫn xuất.

Dưới đây là ví dụ về đa hình động trong C#:

using System;

namespace PolymorphismApplication 
{
   abstract class Shape 
   {
      public abstract int GetArea();
   }
   
   class Rectangle : Shape 
   {
      public int Width { get; set; }
      public int Height { get; set; }
      
      public Rectangle(int width, int height) 
      {
         Width = width;
         Height = height;
      }
      
      public override int GetArea() 
      { 
         return Width * Height; 
      }
   }
   
   class RectangleTester 
   {
      static void Main(string[] args) 
      {
         Rectangle rectangle = new Rectangle(10, 7);
         Console.WriteLine("Area: {0}", rectangle.GetArea());
         Console.ReadKey();
      }
   }
}

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

Area: 70


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#.