Các khái niệm nâng cao trong C#
- 07-12-2023
- Toanngo92
- 0 Comments
C# hỗ trợ nhiều phương pháp và loại hữu ích như các phương thức ẩn danh, phương pháp mở rộng, phương pháp xử lý dữ liệu, và nhiều loại khác nữa. Những phương pháp và loại này đem lại trải nghiệm lập trình hướng đối tượng tốt hơn cho các nhà phát triển C#. Để hỗ trợ việc phát triển ứng dụng đáp ứng nhiều yêu cầu, C# cung cấp những cải tiến ngôn ngữ nâng cao như các delegate được định nghĩa sẵn trong hệ thống, biểu thức lambda, và nhiều thứ khác. Để phát triển các ứng dụng tập trung vào dữ liệu, Framework .NET cung cấp Entity Framework. Để tạo ra các ứng dụng đa người dùng phản hồi nhanh hơn, C# hỗ trợ đa luồng và lập trình song song. Các ứng dụng C# cũng có thể tương tác với ngôn ngữ động, như IronRuby và IronPython. Entity Framework (EF) Core và Entity Framework 6 được sử dụng với C# 9.0.
Trong buổi học này, bạn sẽ học về:
- Mô tả về các phương thức ẩn danh
- Định nghĩa về phương pháp mở rộng
- Giải thích về các loại ẩn danh
- Giải thích về các loại partial
- Giải thích về các loại có thể là null
- Mô tả về các delegate generic được định nghĩa sẵn trong hệ thống
- Định nghĩa về biểu thức lambda
- Giải thích về các biểu thức truy vấn
- Giải thích về lập trình song song và động
- Mô tả về Entity Framework (EF) Core và Entity Framework 6
Mục lục
Phương thức Ẩn Danh
Một phương thức ẩn danh là một đoạn mã không tên được viết ngắn gọn có thể được truyền như một tham số của delegate.
Thường thì, delegates có thể gọi một hoặc nhiều phương thức có tên được bao gồm trong quá trình khai báo delegates. Trước khi có phương thức ẩn danh, để truyền một khối mã nhỏ cho một delegate, nhà phát triển luôn phải tạo một phương thức, sau đó truyền nó cho delegate. Với sự ra đời của phương thức ẩn danh, nhà phát triển có thể truyền một khối mã ngắn gọn cho một delegate mà không cần thực sự tạo ra một phương thức.
Hình bên dưới hiển thị một ví dụ về phương thức ẩn danh.
Đặc điểm
Một phương thức ẩn danh được sử dụng thay thế cho một phương thức có tên nếu phương thức đó chỉ được gọi thông qua một delegate. Một phương thức ẩn danh có những đặc điểm sau:
- Nó xuất hiện như một mã ngắn gọn được viết trực tiếp trong khai báo của delegate.
- Nó phù hợp nhất cho các khối mã nhỏ.
- Nó có thể chấp nhận tham số của bất kỳ loại nào.
- Các tham số sử dụng từ khóa ref và out có thể được truyền cho nó.
- Nó có thể bao gồm các tham số của một loại generic.
- Nó không thể bao gồm các câu lệnh nhảy như goto và break để chuyển quyền điều khiển ra khỏi phạm vi của phương thức.
Tạo Phương thức Ẩn Danh
Một phương thức ẩn danh được tạo khi một delegate được khởi tạo hoặc tham chiếu đến với một khối mã không tên. Những điểm sau đây cần được lưu ý khi tạo phương thức ẩn danh:
- Khi từ khóa delegate được sử dụng bên trong một phương thức, nó phải được theo sau bởi một phương thức ẩn danh.
- Phương thức được định nghĩa như một tập hợp các câu lệnh trong dấu ngoặc nhọn khi tạo một đối tượng của delegate.
- Phương thức ẩn danh không được chỉ định bất kỳ loại trả về nào.
- Phương thức ẩn danh không có tiền tố là các bộ điều khiển truy cập.
Hình bên dưới hiển thị cú pháp cho các phương thức ẩn danh.
Đoạn mã bên dưới tạo ra một phương thức ẩn danh.
using System;
class AnonymousMethods {
delegate void Display();
static void Main(string[] args) {
// Chỗ khác biệt xảy ra khi sử dụng các phương thức ẩn danh
Display objDisp = delegate() {
Console.WriteLine("This illustrates an anonymous method");
};
objDisp();
}
}
Trong đoạn mã trên, một delegate được đặt tên là Display được tạo ra. Delegate Display được khởi tạo bằng một phương thức ẩn danh. Khi delegate được gọi, chính khối mã ẩn danh đó sẽ thực thi.
Kết quả:
This illustrates an anonymous method
Tham chiếu đa phương thức ẩn danh
C# cho phép tạo và khởi tạo một delegate có thể tham chiếu đến nhiều phương thức ẩn danh. Điều này được thực hiện bằng cách sử dụng toán tử +=. Toán tử += được sử dụng để thêm các tham chiếu bổ sung đến các phương thức đã có tên hoặc ẩn danh sau khi khởi tạo delegate.
Đoạn mã bên dưới cho thấy cách một phiên bản có thể tham chiếu một phương thức ẩn danh.
using System;
class MultipleAnonymousMethods
{
delegate void Display();
static void Main(string[] args)
{
// Delegate instantiated with one anonymous method reference
Display objDisp = delegate ()
{
Console.WriteLine("This illustrates one anonymous method");
};
// Delegate instantiated with another anonymous method reference
objDisp += delegate()
{
Console.WriteLine("This illustrates another anonymous method with the same delegate instance");
};
// Invoke both anonymous methods using the delegate
objDisp();
}
}
Trong đoạn mã trên, một phương thức ẩn danh được tạo ra trong quá trình khởi tạo delegate và một phương thức ẩn danh khác được tạo ra và tham chiếu thông qua delegate bằng toán tử +=.
Kết quả:
This illustrates one anonymous method
This illustrates another anonymous method with the same delegate instance
Truyền Tham số
C# cho phép truyền tham số cho các phương thức ẩn danh. Loại tham số có thể được truyền vào một phương thức ẩn danh được xác định tại thời điểm khai báo của delegate. Những tham số này được chỉ định trong dấu ngoặc đơn. Khối mã trong phương thức ẩn danh có thể truy cập những tham số được chỉ định này giống như bất kỳ phương thức thông thường nào. Giá trị của tham số có thể được truyền vào phương thức ẩn danh khi gọi delegate.
Đoạn mã bên dưới mô tả cách truyền tham số cho các phương thức ẩn danh.
using System;
class Parameters
{
delegate void Display(string msg, int num);
static void Main(string[] args)
{
Display objDisp = delegate (string msg, int num)
{
Console.WriteLine(msg + num);
};
objDisp("This illustrates passing parameters to anonymous methods. The int parameter passed is: ", 100);
}
}
Trong đoạn mã trên, một delegate Display được tạo ra. Hai đối số được chỉ định trong khai báo của delegate, một chuỗi và một số nguyên. Sau đó, delegate được khởi tạo bằng một phương thức ẩn danh mà các biến chuỗi và số nguyên được truyền vào như là tham số. Phương thức ẩn danh sử dụng những tham số này để hiển thị kết quả.
Kết quả:
This illustrates passing parameters to anonymous methods. The int parameter passed is: 100
Lưu ý: Nếu định nghĩa của phương thức ẩn danh không bao gồm bất kỳ đối số nào, một cặp dấu ngoặc trống có thể được sử dụng trong khai báo của delegate. Các phương thức ẩn danh với danh sách các tham số không thể được sử dụng với delegates chỉ định các tham số out
Các Phương thức mở rộng
Các phương thức mở rộng cho phép mở rộng một kiểu dữ liệu hiện có với chức năng mới mà không cần sửa đổi trực tiếp các kiểu đó. Các phương thức mở rộng là các phương thức tĩnh phải được khai báo trong một lớp tĩnh. Một phương thức mở rộng có thể được khai báo bằng cách chỉ định tham số đầu tiên với từ khóa this. Tham số đầu tiên trong phương thức này xác định kiểu đối tượng mà phương thức có thể được gọi. Đối tượng được sử dụng để gọi phương thức được tự động truyền làm tham số đầu tiên.
Cú pháp:
static return-type MethodName (this type obj, param-list)
trong đó,
return-type
: kiểu dữ liệu của giá trị trả vềMethodName
: tên của phương thức mở rộngtype
: kiểu dữ liệu của đối tượngparam-list
: danh sách các tham số (tùy chọn)
Đoạn mã bên dưới tạo ra một phương thức mở rộng cho kiểu dữ liệu chuỗi và chuyển đổi ký tự đầu tiên của chuỗi thành chữ thường.
using System;
static class ExtensionExample
{
// Extension Method to convert the first character to lowercase
public static string FirstLetterLower(this string result)
{
if (result.Length > 0)
{
char[] s = result.ToCharArray();
s[0] = char.ToLower(s[0]);
return new string(s);
}
return result;
}
}
class Program
{
public static void Main(string[] args)
{
string country = "Great Britain";
// Calling the extension method
Console.WriteLine(country.FirstLetterLower());
}
}
Trong đoạn mã trên, một phương thức mở rộng có tên là FirstLetterLower
được định nghĩa với một tham số được đi trước bởi từ khóa this
. Phương thức này chuyển đổi chữ cái đầu tiên của bất kỳ câu hoặc từ nào thành chữ thường. Lưu ý rằng phương thức mở rộng được gọi bằng cách sử dụng đối tượng, country
. Giá trị ‘Great Britain’ được tự động truyền vào tham số kết quả. Hình bên dưới mô tả kết quả của đoạn mã bên trên.
Các ưu điểm của phương thức mở rộng như sau:
- Khả năng mở rộng chức năng của kiểu dữ liệu hiện có mà không cần sửa đổi. Điều này sẽ tránh các vấn đề gây hỏng mã nguồn trong các ứng dụng hiện tại.
- Các phương thức bổ sung có thể được thêm vào các giao diện tiêu chuẩn mà không cần thay đổi vật lý trong các thư viện lớp hiện có.
Đoạn mã bên dưới là một ví dụ về một phương thức mở rộng xóa tất cả các giá trị trùng lặp từ một bộ sưu tập generic và hiển thị kết quả. Chương trình này mở rộng lớp generic List
với chức năng được thêm vào.
using System;
using System.Collections.Generic;
static class ExtensionExample
{
// Extension method that accepts and returns a collection
public static List<T> RemoveDuplicates<T>(this List<T> allCities)
{
List<T> finalCities = new List<T>();
foreach (var eachCity in allCities)
{
if (!finalCities.Contains(eachCity))
{
finalCities.Add(eachCity);
}
}
return finalCities;
}
}
class Program
{
public static void Main(string[] args)
{
List<string> cities = new List<string>();
cities.Add("Seoul");
cities.Add("Beijing");
cities.Add("Berlin");
cities.Add("Istanbul");
cities.Add("Seoul");
cities.Add("Istanbul");
cities.Add("Paris");
// Invoke the Extension method, RemoveDuplicates()
List<string> result = cities.RemoveDuplicates();
foreach (string city in result)
{
Console.WriteLine(city);
}
}
}
Trong đoạn mã trên, phương thức mở rộng RemoveDuplicate()
được khai báo và trả về một List
generic khi được gọi. Phương thức này chấp nhận một danh sách generic List<T>
làm đối số đầu tiên:
public static List<T> RemoveDuplicate<T>(this List<T> allCities)
Các dòng mã sau lặp qua mỗi giá trị trong bộ sưu tập, loại bỏ các giá trị trùng lặp và lưu trữ các giá trị duy nhất trong danh sách finalCities:
foreach (var eachCity in allCities)
{
if (!finalCities.Contains(eachCity))
{
finalCities.Add(eachCity);
}
}
Hình bên dưới hiển thị kết quả của đoạn mã trên.
Lưu ý: Mặc dù phương thức mở rộng được khai báo là tĩnh nhưng nó vẫn có thể được gọi bằng cách sử dụng một đối tượng.
Kiểu ẩn danh (Anonymous Types)
Kiểu Anonymous cơ bản là một lớp không có tên và không được định nghĩa rõ ràng trong mã code. Một kiểu Anonymous sử dụng bộ khởi tạo đối tượng để khởi tạo các thuộc tính và trường. Vì nó không có tên, một biến kiểu được gán tự động phải được khai báo để tham chiếu đến nó.
Cú pháp:
new { identifierA = valueA, identifierB = valueB }
trong đó,
identifierA
,identifierB
: Các định danh sẽ được chuyển đổi thành các thuộc tính chỉ đọc được (read-only properties) được khởi tạo với các giá trị.
Đoạn mã bên dưới thể hiện việc sử dụng các kiểu ẩn danh.
using System;
/// <summary>
/// Class AnonymousTypeExample để minh họa kiểu ẩn danh
/// </summary>
class AnonymousTypeExample {
public static void Main(string[] args) {
// Kiểu ẩn danh với ba thuộc tính
var stock = new { Name = "Michgan Enterprises", Code = 1301, Price = 35056.75 };
Console.WriteLine("Stock Name: " + stock.Name);
Console.WriteLine("Stock Code: " + stock.Code);
Console.WriteLine("Stock Price: " + stock.Price);
}
}
Xét đến dòng mã sau:
var stock = new { Name = "Michgan Enterprises", Code = 1301, Price = 35056.75 };
Trình biên dịch tạo ra một kiểu ẩn danh với tất cả các thuộc tính được suy ra từ bộ khởi tạo đối tượng. Trong trường hợp này, kiểu sẽ có các thuộc tính Name
, Code
và Price
. Trình biên dịch tự động tạo ra các phương thức get
và set
, cũng như các biến riêng tư tương ứng để lưu giữ các thuộc tính này. Lúc chạy, trình biên dịch C# tạo ra một thể hiện của kiểu này và các thuộc tính được gán các giá trị lần lượt là Michgan Enterprises, 1301 và 35056.75.
Hình bên dưới hiển thị kết quả của đoạn mã trên.
Khi một kiểu ẩn danh được tạo, trình biên dịch C# thực hiện các nhiệm vụ sau:
- Hiểu rõ kiểu dữ liệu
- Tạo ra một lớp mới
- Sử dụng lớp mới để khởi tạo một đối tượng mới
- Gán đối tượng với các tham số cần thiết
Partial Types
Hãy giả sử rằng một tổ chức lớn có bộ phận Công nghệ thông tin của mình được phân tán ở hai địa điểm, Melbourne và Sydney. Hoạt động tổng thể diễn ra thông qua dữ liệu tổng hợp được thu thập từ cả hai địa điểm. Khách hàng của tổ chức sẽ thấy nó như một thực thể hoàn chỉnh, trong khi thực tế, nó sẽ được tạo thành từ nhiều đơn vị.
Bây giờ, hãy nghĩ về một lớp hoặc cấu trúc C# rất lớn với nhiều định nghĩa thành viên. Các thành viên dữ liệu của lớp hoặc cấu trúc này có thể được chia và lưu trữ trong các tệp tin khác nhau. Những thành viên này có thể được kết hợp thành một đơn vị duy nhất khi thực thi chương trình. Điều này có thể được thực hiện bằng cách tạo các kiểu một phần.
Đặc điểm của Partial Types
Tính năng của Partial Types giúp định nghĩa các lớp, cấu trúc và giao diện qua nhiều tệp tin.
Các Partial Types mang lại nhiều lợi ích. Đó là:
- Chia tách mã sinh ra từ mã ứng dụng.
- Giúp việc phát triển và bảo trì mã code trở nên dễ dàng hơn.
- Làm cho quá trình gỡ lỗi trở nên dễ dàng hơn.
- Ngăn chặn lập trình viên vô tình sửa đổi mã code hiện có.
Hình bên dưới hiển thị một ví dụ về kiểu một phần.
Lưu ý: C# không hỗ trợ định nghĩa kiểu một phần cho các định nghĩa liệt kê (enumerations). Tuy nhiên, các kiểu generic có thể được định nghĩa một phần.
Các phần được kết hợp trong quá trình biên dịch
Các thành viên của các lớp partial, cấu trúc partial hoặc giao diện partial được khai báo và lưu trữ tại các vị trí khác nhau được kết hợp lại vào thời điểm biên dịch. Các thành viên này có thể bao gồm:
- Chú thích XML
- Giao diện
- Tham số kiểu generic
- Biến lớp
- Biến cục bộ
- Sự kiện
- Phương thức
- Thuộc tính
Kiểu một phần có thể được biên dịch tại Command Prompt cho Developer trong VS2017. Lệnh để biên dịch một kiểu một phần là:
csc /out:<FileName>.exe <CSharpFileNameOne>.cs <CSharpFileNameTwo>.cs
trong đó,
- FileName: Là tên tệp .exe được người dùng chỉ định
- CSharpFileNameOne: Là tên của tệp đầu tiên mà kiểu một phần được định nghĩa
- CSharpFileNameTwo: Là tên của tệp thứ hai mà kiểu một phần được định nghĩa.
Cuối cùng, tệp .exe có thể được chạy trực tiếp để xem kết quả cần thiết. Điều này được minh họa trong hai đoạn mã bên dưới.
Đoạn mã thứ nhất:
using System;
using System.Collections.Generic;
using System.Text;
namespace School
{
public partial class StudentDetails
{
int _rollNo;
string _studName;
public StudentDetails(int number, string name)
{
_rollNo = number;
_studName = name;
}
}
}
Đoạn mã thứ hai:
using System;
using System.Collections.Generic;
using System.Text;
namespace School
{
public partial class StudentDetails
{
int _rollNo;
string _studName;
public StudentDetails(int number, string name)
{
_rollNo = number;
_studName = name;
}
public void Display()
{
Console.WriteLine("Student Roll Number: " + _rollNo);
Console.WriteLine("Student Name: " + _studName);
}
}
public class Students
{
static void Main(string[] args)
{
StudentDetails objStudents = new StudentDetails(20, "Frank");
objStudents.Display(); // Thay obj Students.Display(); thành objStudents.Display();
}
}
}
Trong 2 đoạn mã trên, lớp một phần StudentDetails
tồn tại trong hai tệp tin khác nhau. Khi cả hai tệp tin này được biên dịch tại Command Prompt của Visual Studio 2019, một tệp .exe được tạo ra kết hợp lớp StudentDetails
từ cả hai tệp tin. Khi thực thi tệp .exe tại command prompt, số hiệu và tên của sinh viên sẽ được hiển thị làm kết quả. Hình bên dưới minh họa cách biên dịch và thực thi tệp StudentDetails.cs
và Students.cs
được tạo trong các ví dụ sử dụng Developer Command Prompt cho VS2019.
Thực hiện kiểu Partial (Partial Types)
Partial Types được thực hiện bằng cách sử dụng từ khóa partial
. Từ khóa này chỉ ra rằng mã code được chia thành nhiều phần và các phần này được định nghĩa trong các tệp tin và không gian tên khác nhau. Tên kiểu của tất cả các phần thành phần của một mã code một phần đều có tiền tố là từ khóa partial
. Ví dụ, định nghĩa hoàn chỉnh của một cấu trúc được chia thành ba tệp tin, mỗi tệp tin phải chứa một cấu trúc một phần có từ khóa partial
đặt trước tên kiểu. Ngoài ra, mỗi phần một phần của mã code phải có cùng một bộ điều chỉnh truy cập.
Cú pháp sau được sử dụng để chia định nghĩa của một lớp, một cấu trúc hoặc một giao diện:
[access_modifier] [keyword] partial <type> <Identifier>
trong đó,
access_modifier
: Là một bộ điều chỉnh truy cập tùy chọn như public, private, và cứ thế.keyword
: Là một từ khóa tùy chọn như static, abstract, và cứ thế.type
: Là tên của lớp, cấu trúc hoặc giao diện.Identifier
: Đây là tên của class, cấu trúc hoặc giao diện.
Đoạn mã bên dưới đây tạo ra giao diện với hai định nghĩa giao diện partial
using System;
// ProgramName: MathsDemo.cs
partial interface MathsDemo
{
int Addition(int valOne, int valTwo);
}
// ProgramName: MathsDemo2.cs
partial interface MathsDemo
{
int Subtraction(int valOne, int valTwo);
}
class Calculation : MathsDemo
{
public int Addition(int valOne, int valTwo)
{
return valOne + valTwo;
}
public int Subtraction(int valOne, int valTwo)
{
return valOne - valTwo;
}
}
class Program
{
static void Main(string[] args)
{
int numOne = 45;
int numTwo = 10;
Calculation objCalculate = new Calculation();
Console.WriteLine("Addition of two numbers: " + objCalculate.Addition(numOne, numTwo));
Console.WriteLine("Subtraction of two numbers: " + objCalculate.Subtraction(numOne, numTwo));
}
}
Trong đoạn mã trên, một giao diện partial Maths
được tạo ra chứa phương thức Addition. Tệp này được lưu với tên MathsDemo.cs
. Phần còn lại của cùng một giao diện chứa phương thức Subtraction
và được lưu dưới tên tệp MathsDemo2.cs
. Tệp này cũng bao gồm lớp Calculation
, kế thừa giao diện Maths
và triển khai hai phương thức, Addition
và Subtraction
.
Kết quả:
Addition of two numbers: 55
Subtraction of two numbers: 35
Lưu ý: Nếu partial của mã được lưu trong một tệp tin được khai báo là abstract
và các phần khác được khai báo là public
, toàn bộ mã sẽ được coi là abstract
. Quy tắc này cũng áp dụng cho các lớp sealed
.
Lớp Partial (Partial Classes)
Lớp là một trong những loại trong C# hỗ trợ định nghĩa partial. Các lớp có thể được định nghĩa ở nhiều vị trí khác nhau để lưu trữ các thành viên khác nhau như biến, phương thức, v.v. Mặc dù, định nghĩa của lớp được chia thành các phần khác nhau được lưu trữ dưới các tên khác nhau, tất cả các phần này của định nghĩa sẽ được kết hợp trong quá trình biên dịch để tạo ra một lớp duy nhất.
Các lớp một phần có thể được tạo ra để lưu trữ các thành viên private trong một tệp và các thành viên public trong một tệp khác. Quan trọng hơn, nhiều nhà phát triển có thể làm việc trên các phần riêng biệt của một lớp duy nhất đồng thời, nếu lớp đó được phân tán trong các tệp tin riêng biệt.
Đoạn mã bên dưới tạo ra hai lớp partialn để hiển thị tên và số lớp của một sinh viên.
using System;
// Program: StudentDetails.cs
public partial class StudentDetails
{
public void Display()
{
Console.WriteLine("Student Roll Number: " + _rollNo);
Console.WriteLine("Student Name: " + _studName);
}
}
// Program: StudentDetails2.cs
public partial class StudentDetails
{
int _rollNo;
string _studName;
public StudentDetails(int number, string name)
{
_rollNo = number;
_studName = name;
}
}
public class Students
{
static void Main(string[] args)
{
StudentDetails objStudents = new StudentDetails(20, "Frank");
objStudents.Display();
}
}
Trong đoạn mã trên, lớp StudentDetails
có định nghĩa của nó được phân tán qua hai tệp tin, StudentDetails.cs
và StudentDetails2.cs
. Tệp StudentDetails.cs
chứa phần của lớp có chứa phương thức Display()
. StudentDetails2.cs
chứa phần còn lại của lớp bao gồm hàm tạo. Lớp Students
tạo một thể hiện của lớp StudentDetails
và gọi phương thức Display
.
Kết quả hiển thị số lớp và tên của sinh viên:
Student Roll Number: 20
Student Name: Frank
Phương thức Partial (Partial Methods)
Hãy xem xét một lớp một phần Shape
mà định nghĩa hoàn chỉnh của nó được chia thành hai tệp tin. Bây giờ, hãy xem xét rằng một phương thức Create()
có một chữ ký được định nghĩa trong Shape
.
Lớp một phần Shape
chứa định nghĩa của Create()
trong Shape.cs
. Phần còn lại của lớp một phần Shape
hiện diện trong RealShape.cs
và nó chứa triển khai của Create()
. Do đó, Create()
là một phương thức một phần mà định nghĩa của nó được chia thành hai tệp tin.
Một phương thức một phần là một phương thức có chữ ký được bao gồm trong một kiểu partial, như một lớp partial hoặc một cấu trúc. Phương thức có thể được triển khai tùy ý trong một phần khác của lớp partial hoặc kiểu hoặc cùng partial của lớp hoặc kiểu đó.
Một số hạn chế khi làm việc với phương thức một phần như sau:
- Từ khóa
partial
là bắt buộc khi định nghĩa hoặc triển khai một phương thức một phần. - Phương thức một phần phải trả về
void
. - Chúng được mặc định là
private
. - Phương thức một phần có thể trả về
ref
nhưng không thể trả vềout
. - Phương thức một phần không thể có bất kỳ bộ điều chỉnh truy cập nào như
public
,private
và các từ khóa nhưvirtual
,abstract
,sealed
, và cứ thế.
Phương thức partial hữu ích khi một phần của mã đã được tạo tự động bởi một công cụ hoặc IDE và các phần khác của mã yêu cầu tùy chỉnh.
Các kiểu dữ liệu có thể Null (Nullable Types)
C# cung cấp các kiểu dữ liệu có thể null để xác định và xử lý các kiểu trường giá trị với giá trị null. Trước khi tính năng này được giới thiệu, chỉ các kiểu tham chiếu mới có thể được gán trực tiếp giá trị null. Các biến kiểu giá trị với giá trị null được chỉ định bằng cách sử dụng một giá trị đặc biệt hoặc một biến bổ sung. Biến bổ sung này chỉ ra liệu biến cần thiết có null hay không.
Giá trị đặc biệt chỉ hữu ích nếu giá trị quyết định được theo đúng cách trong toàn bộ ứng dụng. Việc tạo và quản lý các trường bổ sung cho các biến như vậy dẫn đến việc sử dụng thêm không gian bộ nhớ và trở nên phiền phức. Những vấn đề này được giải quyết bằng cách giới thiệu các kiểu dữ liệu có thể null.
Tạo kiểu dữ liệu có thể Null
Một kiểu dữ liệu có thể null là một cách để định nghĩa giá trị null cho các kiểu giá trị. Nó chỉ ra rằng một biến có thể có giá trị null. Các kiểu dữ liệu có thể null là các thể hiện của cấu trúc System Nullable<T>
.
Một biến có thể trở thành kiểu có thể null bằng cách thêm dấu hỏi sau kiểu dữ liệu. Hoặc có thể được khai báo bằng cấu trúc generic Nullable<T>
có sẵn trong không gian tên System
.
Đặc điểm
Các kiểu dữ liệu có thể null trong C# có các đặc điểm sau:
- Chúng đại diện cho một kiểu giá trị có thể được gán giá trị null.
- Chúng cho phép gán giá trị tương tự như kiểu giá trị thông thường.
- Chúng trả về giá trị được gán hoặc giá trị mặc định cho kiểu có thể null.
Khi một kiểu dữ liệu có thể null được gán cho một kiểu không thể null và giá trị được gán hoặc giá trị mặc định cần được áp dụng, toán tử ?? được sử dụng.
Lưu ý: Một trong những ứng dụng của kiểu dữ liệu có thể null là tích hợp C# với cơ sở dữ liệu chứa giá trị null trong các trường của bảng. Nếu không có kiểu dữ liệu có thể null, không có cách nào để biểu diễn dữ liệu như vậy một cách chính xác. Ví dụ, nếu một biến bool
chứa một giá trị không phải true hoặc false, không có cách nào để biểu thị điều này.
Thực hiện kiểu dữ liệu có thể Null
Một kiểu dữ liệu có thể null có thể bao gồm bất kỳ phạm vi giá trị nào hợp lệ cho kiểu dữ liệu mà kiểu dữ liệu có thể null thuộc về. Ví dụ, một kiểu bool
được khai báo như một kiểu có thể null có thể được gán các giá trị true, false, hoặc null. Các kiểu dữ liệu có thể null có hai thuộc tính chỉ đọc công khai mà có thể được triển khai để kiểm tra tính hợp lệ của các kiểu dữ liệu có thể null và để truy xuất giá trị của chúng.
Những thuộc tính này là:
- Thuộc tính
HasValue
HasValue
là thuộc tính xác định tính hợp lệ của giá trị trong một biến. Thuộc tính HasValue
trả về true nếu giá trị của biến không phải là null
, ngược lại trả về false.
- Thuộc tính
Value
Thuộc tính Value
xác định giá trị trong một biến có thể null. Khi HasValue
đánh giá thành true, thuộc tính Value
trả về giá trị của biến, ngược lại nó trả về một ngoại lệ.
Đoạn mã bên dưới hiển thị tên, ID và vai trò của nhân viên sử dụng các kiểu dữ liệu có thể null.
using System;
class Employee {
static void Main(string[] args) {
int empId = 10;
string empName = "Patrick";
char? role = null;
Console.WriteLine("Employee ID: " + empId);
Console.WriteLine("Employee Name: " + empName);
if (role.HasValue == true) {
Console.WriteLine("Role: " + role.Value);
} else {
Console.WriteLine("Role: null");
}
}
}
Trong đoạn mã trên, empId
được khai báo là một biến số nguyên và được khởi tạo với giá trị 10, empName
được khai báo là một biến chuỗi và được gán tên là “Patrick”. Thêm vào đó, role
được xác định là một ký tự có thể null
với giá trị null. Kết quả hiển thị vai trò của nhân viên là null
.
Kết quả:
Employee ID: 10
Employee Name: Patrick
Role: null
Toán Tử ??
Một kiểu dữ liệu có thể null có thể chứa một giá trị đã được xác định hoặc giá trị có thể không xác định. Nếu một kiểu dữ liệu có thể null chứa một giá trị null và kiểu dữ liệu này được gán cho một kiểu không thể null, trình biên dịch sẽ tạo ra một ngoại lệ được gọi là System.InvalidOperationException
.
Để tránh vấn đề này, có thể chỉ định một giá trị mặc định cho kiểu dữ liệu có thể null có thể được gán cho một kiểu không thể null bằng cách sử dụng toán tử ??
. Nếu kiểu dữ liệu có thể null chứa một giá trị null, toán tử ??
trả về giá trị mặc định.
Đoạn mã bên dưới thể hiện việc sử dụng toán tử ??.
using System;
class Salary {
static void Main(string[] args) {
double? actualValue = null;
double marketValue = actualValue ?? 0.0;
actualValue = 100.20;
Console.WriteLine("Value: " + actualValue);
Console.WriteLine("Market Value: " + marketValue);
}
}
Trong đoạn mã trên, biến actualValue
được khai báo là kiểu double
với dấu ? và được khởi tạo với giá trị null
. Điều này có nghĩa là actualValue
hiện là một kiểu có thể null với giá trị null
.
Khi gán cho marketValue
, toán tử ?? đã được sử dụng. Điều này sẽ gán marketValue
với giá trị mặc định là 0.0.
Kết quả:
Value: 100.2
Market Value: 0
Chuyển đổi kiểu dữ liệu có thể Null
C# cho phép chuyển đổi bất kỳ kiểu giá trị nào thành kiểu có thể null, hoặc kiểu có thể null thành kiểu giá trị. C# hỗ trợ hai loại chuyển đổi trên các kiểu có thể null:
- Chuyển đổi ngầm định
- Chuyển đổi tường minh
Lưu trữ một kiểu giá trị vào một kiểu có thể null được gọi là chuyển đổi ngầm định.
Chuyển đổi một kiểu có thể null thành kiểu giá trị được gọi là chuyển đổi tường minh.
Xử lý dữ liệu trong C#
Xử lý dữ liệu trong C# có thể thực hiện thông qua các tệp hoặc bằng cách sử dụng công nghệ cơ sở dữ liệu. ADO.NET là một thành phần công nghệ cơ sở dữ liệu của .NET Framework được sử dụng để kết nối hệ thống ứng dụng và máy chủ cơ sở dữ liệu. ADO đại diện cho ActiveX Data Objects. Công nghệ Microsoft ADO.NET bao gồm một tập hợp các lớp để tương tác với nguồn dữ liệu như tệp XML và cơ sở dữ liệu và xử lý truy cập dữ liệu. Dữ liệu sau đó được sử dụng trong các ứng dụng .NET.
ADO.NET
ADO.NET sử dụng XML để lưu trữ và truyền dữ liệu giữa các ứng dụng. Đây là một tiêu chuẩn ngành và cũng cung cấp truy cập nhanh chóng đến dữ liệu cho các ứng dụng desktop và phân tán. Ưu điểm chính của ADO.
Tính linh hoạt và tương tác là những điểm mạnh của ADO.NET.
Một số đặc điểm nổi bật của ADO.NET bao gồm:
- ADO.NET là một API đối tượng duy nhất. Mặc dù có các nhà cung cấp dữ liệu khác nhau để làm việc với các nguồn dữ liệu khác nhau, mô hình lập trình cho tất cả các nhà cung cấp dữ liệu hoạt động theo cùng một cách. Do đó, chỉ cần biết một nhà cung cấp dữ liệu, người dùng có thể thay đổi tên lớp và chuỗi kết nối. Các lớp ADO.NET có tính hướng đối tượng. Do đó, chúng có thể được sử dụng và hiểu một cách dễ dàng.
- Mã và các lớp trong ADO.NET được quản lý. Common Language Runtime (CLR) chịu trách nhiệm cho tính độc lập ngôn ngữ và quản lý tài nguyên tự động.
- Để làm việc trong hình thức trực quan, .NET cung cấp các thành phần ADO.NET và các điều khiển dữ liệu được ràng buộc. Các thành phần này có thể được sử dụng mà không cần lập trình mở rộng. Kết quả đạt được rất nhanh chóng.
- Hiệu suất và khả năng mở rộng là một vai trò quan trọng khi phát triển ứng dụng dựa trên Web và dịch vụ. Dữ liệu được lưu trữ cache tách rời trong XML giúp đạt được hiệu suất và khả năng mở rộng tối ưu.
Dưới đây là một số loại ứng dụng .NET sử dụng ADO.NET để kết nối với cơ sở dữ liệu, thực thi các lệnh và truy xuất dữ liệu:
- Ứng dụng Web ASP.NET
- Ứng dụng Windows
- Ứng dụng Console
Đoạn mã bên dưới thể hiện cách kết nối vào cơ sở dữ liệu SQL Server và truy xuất dữ liệu. Đây là một đoạn mã ADO.NET mẫu để kết nối vào cơ sở dữ liệu SQL Server và truy xuất dữ liệu. Ở đây, các lớp SqlConnection
, SqlCommand
và SqlDataReader
được sử dụng. Tất cả các lớp này đều có sẵn trong không gian tên System.Data.SqlClient
. Do đó, có thể giả định rằng nhà cung cấp dữ liệu .NET cho SQL Server là System.Data.SqlClient
.
SqlConnection con = new SqlConnection("data source=.; database=Sample; integrated security=SSPI");
SqlCommand cmd = new SqlCommand("Select * from tb1Product", con);
con.Open();
SqlDataReader rdr = cmd.ExecuteReader();
// Đối tượng SqlDataReader bây giờ chứa dữ liệu được truy xuất
// Thực hiện một số công việc với dữ liệu
con.Close();
Entity Framework
Entity Framework (EF) là một bộ công nghệ trong ADO.NET hỗ trợ việc phát triển ứng dụng phần mềm hướng dữ liệu. Đây là một framework Object Relational Mapper (ORM) mã nguồn mở dành cho ứng dụng .NET. Entity Framework cho phép các nhà phát triển làm việc với dữ liệu thông qua các đối tượng của các lớp cụ thể cho domain (miền). Không cần tập trung vào các bảng và cột cơ sở dữ liệu để lưu trữ dữ liệu này. Bằng cách sử dụng Entity Framework, nhà phát triển có thể đạt được mức độ trừu tượng cao khi làm việc với dữ liệu. Hơn nữa, họ có thể tạo ra các ứng dụng hướng dữ liệu và duy trì chúng với lượng mã ít hơn so với các ứng dụng truyền thống.
Trong các ứng dụng hướng dữ liệu, việc mô hình hóa thực thể và mối quan hệ cũng như logic của các vấn đề kinh doanh đang được giải quyết và làm việc với các hệ thống lưu trữ và truy xuất dữ liệu là cần thiết. Dữ liệu có thể lan rộng qua nhiều hệ thống lưu trữ, mỗi hệ thống có giao thức riêng. Hơn nữa, các ứng dụng làm việc với một hệ thống lưu trữ duy nhất cũng cần cân nhắc yêu cầu của hệ thống lưu trữ so với yêu cầu viết mã ứng dụng hiệu quả và dễ bảo trì. Tất cả những yêu cầu này đều được đáp ứng trong Entity Framework.
Thông qua Entity Framework, dữ liệu có thể được xử lý dưới dạng các đối tượng và thuộc tính cụ thể cho domain, ví dụ như khách hàng và địa chỉ của khách hàng. Entity Framework cung cấp lợi ích cho các nhà phát triển làm việc ở mức độ trừu tượng cao khi xử lý dữ liệu mà không cần tập trung vào các bảng và cột cơ sở dữ liệu cơ bản lưu trữ dữ liệu. Hơn nữa, có thể tạo ứng dụng hướng dữ liệu với lượng mã ít hơn so với các ứng dụng truyền thống và mã có thể dễ dàng bảo trì.
Bảng bên dưới liệt kê các thành phần khác nhau của kiến trúc Entity Framework.
Thành Phần | Mô Tả |
Conceptual Model | Độc lập với thiết kế bảng cơ sở dữ liệu. Bao gồm các lớp mô hình cùng với các mối quan hệ của chúng. |
Storage Model | Đại diện cho mô hình thiết kế cơ sở dữ liệu gồm views, bảng và các thủ tục lưu trữ cùng với mối quan hệ của chúng. |
Mapping | Chi tiết về cách ánh xạ mô hình khái niệm và mô hình lưu trữ. |
ADO.NET Data Provider | Tương tác với cơ sở dữ liệu. |
Object Service | Là điểm truy cập chính để lấy và trả về dữ liệu từ cơ sở dữ liệu. Nó chịu trách nhiệm chuyển đổi dữ liệu trả về thành cấu trúc đối tượng thực thể. Quá trình này được biết đến là quá trình vật liệu (materialization process). |
LINQ-to-Entities (L2E) | Là một ngôn ngữ truy vấn dùng trong Entity Framework để truy xuất các thực thể trong mô hình khái niệm (conceptual model). |
Entity SQL | Entity SQL là một ngôn ngữ truy vấn khác, chỉ được sử dụng trong EF 6. Nó tương tự như LINQ-to-Entities (L2E) nhưng khó học hơn so với L2E. |
Entity Client Data Provider | Chuyển đổi các truy vấn Entity SQL hoặc L2E thành SQL, mà cơ sở dữ liệu có thể hiểu được. Nó cũng tương tác với ADO.NET data provider. |
Dưới đây là một số tính năng của EF:
- EF tạo ra mô hình dữ liệu thực thể (EDM) dựa trên các thực thể Plain Old CLR Object (POCO) với các thuộc tính get/set của các loại dữ liệu khác nhau. Mô hình dữ liệu mô tả cấu trúc dữ liệu, như thực thể và mối quan hệ, bất kể cách dữ liệu sẽ thực sự được lưu trữ. Mô hình này được sử dụng để truy vấn hoặc lưu trữ dữ liệu thực thể vào cơ sở dữ liệu cơ bản.
- EF cho phép sử dụng các truy vấn LING bằng C# để lấy dữ liệu từ cơ sở dữ liệu cơ bản. Các truy vấn LING được dịch sang ngôn ngữ truy vấn cụ thể của cơ sở dữ liệu, ví dụ như SQL cho cơ sở dữ liệu quan hệ, bởi nhà cung cấp cơ sở dữ liệu. EF cũng cho phép thực hiện trực tiếp các truy vấn SQL thô trực tiếp vào cơ sở dữ liệu.
- Quản lý giao dịch tự động xảy ra khi truy vấn hoặc lưu trữ dữ liệu. EF cũng cung cấp các tùy chọn để tùy chỉnh quản lý giao dịch.
- EF theo dõi các thay đổi đã xảy ra với các thực thể (giá trị thuộc tính) mà được cung cấp cho cơ sở dữ liệu.
- EF cung cấp mức độ caching cấp đầu tiên ngay khi khởi động. Do đó, việc truy vấn lặp lại sẽ trả về dữ liệu từ bộ nhớ cache thay vì tác động đến cơ sở dữ liệu. Điều này có thể tăng tốc độ xử lý dữ liệu trong ứng dụng.
EF Core và EF 6
Hiện nay, có hai phiên bản khác nhau của Entity Framework, gọi là Entity Framework Core 5.0 và Entity Framework 6.
Lưu ý: EF Core G hiện vẫn chưa ổn định.
EF Core là một bộ ánh xạ đối tượng-cơ sở dữ liệu cho .NET và hỗ trợ các truy vấn LINQ, cập nhật và di chuyển schema cùng nhiều tính năng khác. EF Core hoạt động với các cơ sở dữ liệu như SQL Server/Azure SQL Database, SQLite, Azure Cosmos DB, MySQL, và nhiều hơn nữa.
Cả hai phiên bản này gần như có cùng mục tiêu nhưng khác nhau về tính năng ở nhiều cách. Hiện tại, các tính năng mới của Entity Framework chỉ được thêm vào Entity Framework Core, nhưng có nhiều tính năng của Entity Framework 6 chưa được thêm vào Entity Framework Core.
EF Core 5.0 không thể sử dụng trong ứng dụng .NET Framework. Điều này là một thay đổi lớn.
Một số tính năng của Entity Framework 6 bao gồm:
- Chia thực thể: Cho phép ánh xạ một lớp mô hình dữ liệu vào nhiều bảng cơ sở dữ liệu. Điều này hữu ích trong các tình huống dữ liệu được lưu trữ trên nhiều bảng có quan hệ một một hoặc một không một.
Ví dụ, thông tin cá nhân của sinh viên được lưu trong một bảng và thông tin thành tích của sinh viên được lưu trong một bảng khác và cả hai bảng đều có liên kết với nhau. Trong trường hợp này, một mô hình dữ liệu có thể được sử dụng cho cả hai bảng.
- Tạo/Cập nhật mô hình từ cơ sở dữ liệu: Cho phép tạo ra cơ sở dữ liệu. Để làm điều này, người dùng cần mở tệp .edmx để hiển thị biểu đồ mô hình và chuột phải bất kỳ đâu trên bề mặt thiết kế, sau đó chọn Cập nhật Mô hình từ Cơ sở dữ liệu.
- Hiển thị đồ họa của mô hình: Cho phép chỉnh sửa mô hình dữ liệu thực thể (.edmx) bằng các công cụ thiết kế. Tính năng này chỉ được hỗ trợ trong Entity Framework 6 và không có trong Entity Framework Core.
- Ánh xạ thủ tục lưu trữ: Entity Framework 6 Code-First cung cấp khả năng tạo và sử dụng thủ tục lưu trữ cho các thao tác thêm, cập nhật và xóa khi phương thức
SaveChanges()
được gọi. Người dùng có thể ánh xạ một mô hình bằng cách gọi phương thứcMapToStoredProcedures()
để ánh xạ một thực thể với các thủ tục lưu trữ mặc định. Ba thủ tục lưu trữ mặc định tương ứng theo quy ước đặt tên sẽ được tạo tự động – ví dụ,sp_InsertBook
,sp_UpdateBook
, vàsp_DeleteBook
. Người dùng cũng có thể truyền tên thủ tục lưu trữ của riêng họ nếu họ không muốn sử dụng các thủ tục mặc định.
Các tính năng của Entity Framework Core 5.0 bao gồm:
- Alternate keys: EF Core sử dụng SQL để tạo và đọc cơ sở dữ liệu, vì vậy một khóa thay thế phục vụ như một bộ nhận diện thay thế duy nhất cho mỗi thực thể ngoài khóa chính.
- Key generation: Tính năng này được sử dụng để tạo các khóa cần thiết trước khi chèn bất kỳ dữ liệu nào vào cơ sở dữ liệu.
- Truy vấn SQL và tạo với LINQ: Entity Framework Core cho phép người dùng sử dụng các truy vấn SQL thô khi làm việc với cơ sở dữ liệu quan hệ. Những truy vấn này hữu ích nếu truy vấn mà người dùng cần không thể được biểu thị bằng LINQ. Chúng có thể trả về các loại thực thể thông thường hoặc các loại thực thể không có khóa thuộc mô hình. SQL thô có thể được thực thi trên các đối tượng thực thể, miễn là các thuộc tính trả về SQL khớp với thuộc tính thực thể.
Ví dụ:
var books = context.Books
.FromSql("SELECT * FROM dbo.Books")
.ToList();
- Filtered include: Từ Entity Framework Core 5.0 trở đi, phương thức Include hỗ trợ việc lọc.
Ví dụ:
var books = context.Books
.Include(e => e.Titles.Where(p => p.Author.Contains("James")))
.ToList();
Truy vấn này sẽ trả về sách cùng với từng tiêu đề liên quan, nhưng chỉ khi tác giả chứa “James”.
Đề xuất rằng các nhà phát triển nên sử dụng EF Core trên .NET 5.0 cho tất cả các ứng dụng mới.
Biểu thức Lambda
Phương thức liên kết với một đối tượng delegate không thể tự gọi, thay vào đó chỉ có thể gọi thông qua đối tượng delegate. Đôi khi, việc tạo một phương thức riêng chỉ để có thể gọi thông qua đối tượng delegate có thể rất phức tạp. Để giải quyết vấn đề này, người ta có thể sử dụng các phương thức ẩn danh và biểu thức lambda. Các phương thức ẩn danh cho phép tạo các khối mã không tên để đại diện cho một phương thức được tham chiếu bởi đối tượng delegate. Một biểu thức lambda là một biểu thức ẩn danh có thể chứa các biểu thức và câu lệnh, giúp đơn giản hóa quá trình lập trình thông qua mã nguồn nội tuyến. Nói một cách đơn giản, biểu thức lambda là một biểu thức nội tuyến hoặc khối lệnh có cú pháp gọn nhẹ và có thể sử dụng ở bất cứ đâu mà một delegate hoặc phương thức ẩn danh được mong đợi.
Cú pháp:
parameter-list => expression or statements.
trong đó,
parameter-list
: Là danh sách tham số được khai báo rõ ràng hoặc ngụ ý.=>
: Là toán tử lambda.expression or statements
: Có thể là một biểu thức hoặc một hoặc nhiều câu lệnh.
Ví dụ, đoạn mã sau là một biểu thức lambda:
word => word.Length
Xem xét một ví dụ hoàn chỉnh để minh họa việc sử dụng biểu thức lambda. Giả sử một nhà phát triển muốn tính bình phương của một số nguyên. Nhà phát triển có thể sử dụng một phương thức Square()
và truyền tên phương thức này như một tham số cho phương thức Console.WriteLine()
.
Đoạn mã bên dưới sử dụng biểu thức lambda để tính bình phương của một số nguyên.
class Program {
delegate int ProcessNumber(int input)
static void Main(string[] args) {
ProcessNumber del = input => input * input
Console.WriteLine(del(5));
}
}
Toán tử =>
được phát âm là “đi đến” hoặc “đi tới” trong trường hợp có nhiều tham số. Ở đây, biểu thức input => input * input có nghĩa là, cho giá trị của input, tính input nhân với input và trả về kết quả. Ví dụ này trả về bình phương của một số nguyên.
Biểu thức Lambdas
Biểu thức lambda kiểu Expression là biểu thức lambda với một biểu thức được đặt bên phải của nó. Nó có cú pháp như sau:
(input_parameters) => expression
trong đó,
input_parameters
: Một hoặc nhiều tham số đầu vào, mỗi tham số cách nhau bằng dấu phẩy.expression
: Biểu thức cần được đánh giá.
Biểu thức lambda có thể có tham số nhập vào được xác định ngầm hoặc được xác định rõ ràng.
Khi có hai hoặc nhiều tham số nhập vào ở bên trái của toán tử lambda, chúng phải được đặt trong dấu ngoặc. Nếu không có tham số nào cả, một cặp dấu ngoặc trống phải được sử dụng. Tuy nhiên, nếu chỉ có một tham số nhập vào và kiểu của nó được biết ngầm, thì dấu ngoặc có thể được bỏ qua.
Xem xét biểu thức lambda:
(str, strl) => str == strl
Nó có nghĩa là str
và strl
sẽ được sử dụng trong biểu thức so sánh, so sánh str
với strl
. Nó đơn giản là có nghĩa là các tham số str
và strl
sẽ được truyền vào biểu thức str == str
.
Ở đây, không rõ ràng về các kiểu của str
và strl
. Do đó, tốt nhất là chỉ rõ kiểu dữ liệu của chúng.
(string str, string str1) => str == str1
Để sử dụng biểu thức lambda, khai báo một loại delegate tương thích với biểu thức lambda. Sau đó, tạo một thể hiện của delegate và gán biểu thức lambda cho nó. Sau đó, người phát triển sẽ gọi thực thi thể hiện delegate với các tham số, nếu có. Điều này sẽ dẫn đến việc thực thi biểu thức lambda. Kết quả của biểu thức sẽ là giá trị được trả về bởi lambda.
Đoạn mã bên dưới minh họa biểu thức lambda của biểu thức.
using System;
/// <summary>
/// Class ConvertString converts a given string to uppercase
/// </summary>
public class ConvertString
{
delegate string MakeUpper(string s);
public static void Main()
{
// Assign a lambda expression to the delegate instance
MakeUpper con = word => word.ToUpper();
// Invoke the delegate in Console.WriteLine with a string parameter
Console.WriteLine(con("abc"));
}
}
Trong đoạn mã trên, một delegate được đặt tên là MakeUpper
được tạo và khởi tạo. Lúc khởi tạo, một biểu thức lambda, word => word.ToUpper()
, được gán cho thể hiện delegate. Ý nghĩa của biểu thức lambda này là với một chuỗi đầu vào là word
, gọi phương thức ToUpper()
lên nó. ToUpper()
là một phương thức của lớp String
và chuyển đổi một chuỗi đã cho thành chữ in hoa.
Lưu ý: Khi gán một biểu thức lambda vào một thể hiện delegate, biểu thức lambda phải tương thích với thể hiện delegate.
Phương thức Lambdas
Lambda có câu lệnh là một lambda với một hoặc nhiều câu lệnh. Nó có thể bao gồm vòng lặp, câu lệnh if
, và những điều tương tự.
(input_parameters) => {statement; }
trong đó,
input_parameters
: Một hoặc nhiều tham số đầu vào, mỗi tham số cách nhau bởi dấu phẩy.statement
: Một khối câu lệnh chứa một hoặc nhiều câu lệnh.
Tùy chọn, người phát triển có thể chỉ định một câu lệnh return để lấy kết quả của lambda.
(string str, string str1) => { return (str==str1);}
Đoạn mã bên dưới minh họa một biểu thức lambda có câu lệnh.
///ClassWordLength xác định độ dài của một từ hoặc cụm từ cho trước
public class WordLength {
// Khai báo một delegate không trả về giá trị nhưng nhận vào một chuỗi
delegate void GetLength(string s);
public static void Main() {
// Ở đây, thân của lambda bao gồm hai câu lệnh hoàn chỉnh
GetLength len = name => {
int n = name.Length;
Console.WriteLine(n.ToString());
};
// Gọi delegate
len("Mississippi");
}
}
Lambdas với toán tử truy vấn tiêu chuẩn
Đây là một số toán tử truy vấn chuẩn thường được sử dụng với biểu thức lambda trong C#:
Toán tử | Mô tả |
Sum | Tính tổng của các phần tử trong biểu thức |
Count | Đếm số lượng phần tử trong biểu thức |
OrderBy | Sắp xếp các phần tử trong biểu thức |
Contains | Xác định xem một giá trị đã cho có xuất hiện trong biểu thức hay không |
Đoạn mã bên dưới thể hiện cách sử dụng toán tử OrderBy
kết hợp với toán tử lambda để sắp xếp một danh sách các tên.
using System;
using System.Linq;
/// <summary>
/// Class NameSort sorts a list of names
/// </summary>
public class NameSort
{
public static void Main()
{
// Declare and initialize an array of strings
string[] names = { "Hanna", "Jim", "Peter", "Karl", "Abby", "Benjamin" };
// Sort the array of names using OrderBy method and print each name
foreach (string name in names.OrderBy(name => name))
{
Console.WriteLine(name);
}
}
}
Đoạn mã bên dưới sắp xếp danh sách các tên trong mảng chuỗi theo thứ tự ABC rồi hiển thị chúng một cách tuần tự. Nó sử dụng toán tử lambda cùng với toán tử truy vấn chuẩn là Order By
.
Hình bên dưới hiển thị kết quả của ví dụ sử dụng toán tử OrderBy
:
Biểu thức truy vấn
Một truy vấn là một tập hợp các hướng dẫn truy xuất dữ liệu từ nguồn dữ liệu. Nguồn có thể là bảng cơ sở dữ liệu, bảng tập dữ liệu ADO.NET, một tệp XML, hoặc thậm chí là một bộ sưu tập các đối tượng như danh sách các chuỗi. Một biểu thức truy vấn là một truy vấn được viết bằng cú pháp truy vấn sử dụng các mệnh đề như từ khóa from
, select
, và những từ tương tự. Những mệnh đề này là một phần không thể thiếu của một truy vấn LINQ. LINQ là một bộ công nghệ được giới thiệu trong Visual Studio 2008. Nó giúp đơn giản hóa việc làm việc với dữ liệu có định dạng khác nhau trong các nguồn dữ liệu khác nhau. LINQ cung cấp một mô hình nhất quán để làm việc với dữ liệu như vậy.
Thiết lập thông qua LINQ, các nhà phát triển có thể làm việc với truy vấn như một phần của ngôn ngữ C#. Họ có thể tạo và sử dụng các biểu thức truy vấn, được sử dụng để truy vấn và biến đổi dữ liệu từ một nguồn dữ liệu được hỗ trợ bởi LINQ. Một mệnh đề from
phải được sử dụng để bắt đầu một biểu thức truy vấn và một mệnh đề select
hoặc group
phải được sử dụng để kết thúc biểu thức truy vấn.
Đoạn mã bên dưới hiển thị một ví dụ đơn giản về biểu thức truy vấn. Ở đây, một tập hợp các chuỗi đại diện tên được tạo và sau đó, một biểu thức truy vấn được xây dựng để chỉ truy xuất những tên kết thúc bằng ‘I’.
class Program
{
static void Main(string[] args)
{
string[] names = { "Hanna", "Jim", "Pearl", "Mel", "Jill", "Peter", "Karl", "Abby", "Benjamin" };
IEnumerable<string> words = from word in names
where word.EndsWith("l")
select word;
foreach (string s in words)
{
Console.WriteLine(s);
}
}
}
Hình bên dưới hiển thị kết quả của ví dụ biểu thức truy vấn.
Khi trình biên dịch gặp một biểu thức truy vấn, nó chuyển đổi nó thành một cuộc gọi phương thức, sử dụng các phương pháp mở rộng.
Do đó, một biểu thức như mẫu trong đoạn mã bên dưới được chuyển đổi một cách phù hợp.
IEnumerable<string> words = from word in names
where word.EndsWith("I")
select word;
Sau khi chuyển đổi:
IEnumerable<string> words = names.Where(word => word.EndsWith("I"));
Mặc dù dòng này gọn hơn, cách viết biểu thức truy vấn theo kiểu SQL dễ đọc và dễ hiểu hơn nhiều.
Một số từ khóa truy vấn thông thường được sử dụng trong biểu thức truy vấn được liệt kê trong bảng bên dưới.
Từ khoá | Mô tả |
from | Được sử dụng để chỉ ra nguồn dữ liệu và một biến phạm vi |
where | Được sử dụng để lọc các phần tử nguồn dữ liệu dựa trên một hoặc nhiều biểu thức logic có thể được phân tách bằng các toán tử && hoặc || |
select | Được sử dụng để chỉ ra cách các phần tử trong chuỗi kết quả sẽ trông như thế nào khi truy vấn được thực thi |
group | Được sử dụng để nhóm kết quả truy vấn dựa trên một giá trị khóa được chỉ định |
orderby | Được sử dụng để sắp xếp kết quả truy vấn theo thứ tự tăng hoặc giảm dần |
ascending | Được sử dụng trong mệnh đề orderby để biểu diễn thứ tự tăng dần khi sắp xếp |
descending | Được sử dụng trong mệnh đề orderby để biểu diễn thứ tự giảm dần khi sắp xếp |
Truy cập Cơ sở dữ liệu Sử dụng Framework Entity
Hầu hết các ứng dụng C# lưu trữ và truy xuất dữ liệu có thể được lưu trữ trong một số nguồn dữ liệu, chẳng hạn như cơ sở dữ liệu quan hệ, tệp XML hoặc bảng tính. Trong một ứng dụng, dữ liệu thường được biểu diễn dưới dạng các lớp và đối tượng. Tuy nhiên, trong cơ sở dữ liệu, dữ liệu được lưu trữ dưới dạng các bảng và chế độ xem. Do đó, một ứng dụng để lưu trữ và truy xuất dữ liệu trước hết cần kết nối với kho dữ liệu. Ứng dụng cũng phải đảm bảo rằng các định nghĩa và mối quan hệ của các lớp hoặc đối tượng được ánh xạ với các bảng và mối quan hệ bảng cơ sở dữ liệu. Cuối cùng, ứng dụng phải cung cấp mã truy xuất dữ liệu để lưu trữ và truy xuất dữ liệu. Tất cả những thao tác này có thể được thực hiện bằng cách sử dụng ADO.NET, đó là một tập hợp các thư viện cho phép một ứng dụng tương tác với nguồn dữ liệu. Tuy nhiên, trong các ứng dụng trung tâm dữ liệu doanh nghiệp, việc sử dụng ADO.NET dẫn đến sự phức tạp trong phát triển, từ đó tăng thời gian và chi phí phát triển. Ngoài ra, khi mã truy xuất dữ liệu của một ứng dụng tăng lên, ứng dụng trở nên khó bảo trì và thường dẫn đến chi phí hiệu suất.
Để đáp ứng yêu cầu truy cập dữ liệu của các ứng dụng doanh nghiệp, các Framework ánh xạ mối quan hệ đối tượng (ORM) đã được giới thiệu. Một Framework ORM đơn giản hóa quá trình truy xuất dữ liệu từ các ứng dụng. Một Framework ORM thực hiện các chuyển đổi cần thiết giữa các hệ thống loại không tương thích trong cơ sở dữ liệu quan hệ và các ngôn ngữ lập trình hướng đối tượng.
Framework Entity là một Framework ORM mà ứng dụng .NET có thể sử dụng.
Mô hình dữ liệu thực thể
Framework Entity là một triển khai của Mô hình Dữ liệu Thực thể (EDM), đó là một mô hình khái niệm mô tả các thực thể và các mối quan hệ mà chúng tham gia trong một ứng dụng. EDM cho phép một lập trình viên xử lý logic truy cập dữ liệu bằng cách lập trình đối với các thực thể mà không cần phải lo lắng về cấu trúc của kho dữ liệu cơ sở và cách kết nối với nó. Ví dụ, trong một hoạt động đặt hàng của một ứng dụng quản lý mối quan hệ khách hàng, một lập trình viên sử dụng EDM có thể làm việc với các thực thể Khách hàng và Đơn hàng theo cách hướng đối tượng mà không cần viết mã kết nối cơ sở dữ liệu hoặc mã truy xuất dữ liệu dựa trên SQL.
Hình bên dưới hiển thị vai trò của EDM trong kiến trúc Framework Entity.
Các Phương Pháp Phát Triển
Entity Framework loại bỏ việc viết phần lớn mã truy cập dữ liệu mà thông thường cần phải được viết. Nó sử dụng các phương pháp khác nhau để quản lý dữ liệu liên quan đến một ứng dụng. Các phương pháp này bao gồm:
- Phương pháp dựa trên cơ sở dữ liệu
Entity Framework tạo ra mô hình dữ liệu chứa tất cả các lớp và thuộc tính tương ứng với các đối tượng cơ sở dữ liệu hiện có, chẳng hạn như các bảng và cột.
- Phương pháp dựa trên mô hình
Entity Framework tạo ra các đối tượng cơ sở dữ liệu dựa trên mô hình mà một lập trình viên tạo ra để đại diện cho các thực thể và mối quan hệ của chúng trong ứng dụng.
- Phương pháp dựa trên mã
Entity Framework tạo ra các đối tượng cơ sở dữ liệu dựa trên các lớp tùy chỉnh mà một lập trình viên tạo ra để đại diện cho các thực thể và mối quan hệ của chúng trong ứng dụng.
Tạo Mô Hình Dữ Liệu Thực Thể
Visual Studio 2019 cung cấp hỗ trợ để tạo và sử dụng EDM trong ứng dụng C#. Người lập trình có thể sử dụng trình tự wizard Mô Hình Dữ Liệu Thực Thể để tạo một mô hình trong ứng dụng. Sau khi tạo một mô hình, người lập trình có thể thêm các thực thể vào mô hình và xác định mối quan hệ của chúng bằng cách sử dụng trình thiết kế Entity Framework. Thông tin của mô hình được lưu trữ trong một tệp .edmx
. Dựa trên mô hình, người lập trình có thể sử dụng Visual Studio 2019 để tự động tạo ra các đối tượng cơ sở dữ liệu tương ứng với các thực thể. Cuối cùng, người lập trình có thể sử dụng các truy vấn LINQ đối với các thực thể để truy xuất và cập nhật dữ liệu trong cơ sở dữ liệu cơ bản.
Để tạo mô hình dữ liệu thực thể và tạo các đối tượng cơ sở dữ liệu, người lập trình cần thực hiện các bước sau:
- Mở Visual Studio 2019.`
- Tạo một dự án Ứng dụng Console, đặt tên là EDMDemo.
- Chuột phải vào EDMDemo trong cửa sổ Explorer Solution, chọn Add->New Item. Hộp thoại Add New Item – EDMDemo hiển thị.
- Chọn Data từ menu bên trái và sau đó chọn ADO.NET Entity Data Model, như được hiển thị trong Hình bên dưới.
5. Nhấp vào Add. Trình tự Entity Data Model Wizard xuất hiện.
6. Chọn Empty model.
7. Nhấp Finish. Trình thiết kế Mô hình Dữ liệu Thực thể hiển thị.
8. Chuột phải vào Entity Data Model Designer và chọn Add New->Entity. Hộp thoại Add Entity hiển thị.
9. Nhập “Customer” vào ô Entity name và “CustomerID” vào ô tên Property, như hiển thị trong Hình bên dưới.
10. Nhấp vào OK. Trình thiết kế mô hình dữ liệu thực thể hiển thị thực thể new Customer, như trong Hình bên dưới
11. Chuột phải vào thực thể Khách hàng và chọn Add New->Scalar property.
12. Nhập Name là tên của thuộc tính.
13. Tương tự, thêm một thuộc tính Address cho thực thể Customer.
14. Thêm một thực thể khác có tên là Order với một thuộc tính khóa OrderID.
15. Thêm một thuộc tính Chi phí vào thực thể Order.
Xác định Mối quan hệ
Sau khi tạo EDM và thêm các thực thể vào EDM, mối quan hệ giữa các thực thể có thể được xác định bằng cách sử dụng Entity Data Model Designer. Vì một khách hàng có thể có nhiều đơn hàng, thực thể Khách hàng sẽ có một mối quan hệ một-nhiều với thực thể Đơn hàng. Để tạo một liên kết giữa các thực thể Khách hàng và Đơn hàng:
- Chuột phải vào Entity Data Model Designer và chọn Add New->Association. Hộp thoại Add Association được hiển thị.
- Đảm bảo rằng phần cuối ở bên trái của mối quan hệ trỏ vào Order với dạng là 1 (One) và phần cuối ở bên phải trỏ vào Customer với dạng là *(Many).
Chấp nhận cài đặt mặc định cho các trường khác, như được hiển thị trong hình bên dưới.
3. Nhấp OK. Entity Data Model Designer hiển thị các thực thể với mối quan hệ được xác định, như được hiển thị trong hình bên dưới.
Tạo Đối tượng Cơ sở dữ liệu
Sau khi thiết kế mô hình của ứng dụng, người lập trình phải tạo ra các đối tượng cơ sở dữ liệu dựa trên mô hình đó. Để tạo ra các đối tượng cơ sở dữ liệu:
- Chuột phải vào Entity Data Model Designer và chọn Generate Database from Model. Hộp thoại Generate Database Wizard hiển thị.
- Nhấp New Connection. Hộp thoại Connection Properties được hiển thị.
- Nhập (localdb)\v11.0 vào ô Server name và EDMDEMO.CRMDB vào ô chọn hoặc nhập database name, như được hiển thị trong hình bên dưới.
4. Nhấp. OK. Visual Studio sẽ hỏi liệu có tạo cơ sở dữ liệu mới không.
- Nhấp Yes.
- Nhấp Next trong cửa sổ Generate Database Wizard. Visual Studio tạo ra các kịch bản để tạo các đối tượng cơ sở dữ liệu.
- Nhấp Finish. Visual Studio mở tệp chứa các kịch bản để tạo các đối tượng cơ sở dữ liệu.
- Chuột phải vào tệp và chọn Execute. Hộp thoại Kết nối tới Máy chủ hiển thị.
- Nhấp Connect. Visual Studio tạo ra các đối tượng cơ sở dữ liệu.
Sử dụng EDM
Khi một lập trình viên sử dụng Visual Studio để tạo EDM với các thực thể và mối quan hệ của chúng, Visual Studio tự động tạo ra một số lớp. Các lớp quan trọng mà một lập trình viên sẽ sử dụng như sau:
- Lớp Database Context
Lớp này mở rộng lớp DbContext
của không gian tên System.Data.Entity
để cho phép một lập trình viên truy vấn và lưu dữ liệu trong cơ sở dữ liệu. Trong Dự án EDMDemo, lớp Model1Container
có mặt trong tệp Model1.Context.cs
là lớp Database Context.
- Các Lớp Thực thể
Các lớp này đại diện cho các thực thể mà các lập trình viên thêm và thiết kế trong Entity Data Model Designer
. Trong Dự án EDMDemo, Customer và Order là các lớp thực thể.
Đoạn mã bên dưới hiển thị phương thức Main()
tạo và lưu trữ các thực thể Customer
và Order
.
using System;
class Program
{
static void Main(string[] args)
{
using (Model1Container dbContext = new ModelContainer())
{
Console.Write("Enter Customer name: ");
var name = Console.ReadLine();
Console.Write("Enter Customer Address: ");
var address = Console.ReadLine();
Console.Write("Enter Order Cost: ");
var cost = Console.ReadLine();
var customer = new Customer { Name = name, Address = address };
var order = new Order { Cost = cost };
customer.Orders.Add(order);
dbContext.Customers.Add(customer);
dbContext.SaveChanges();
Console.WriteLine("Customer and Order Information added successfully.");
}
}
}
Đoạn mã bên trên yêu cầu và chấp nhận thông tin khách hàng và đơn hàng từ bảng điều khiển. Sau đó, các đối tượng Customer
và Order
được tạo và khởi tạo với dữ liệu. Đối tượng Order
được thêm vào thuộc tính Orders
của đối tượng Customer
. Thuộc tính Orders
, có kiểu dữ liệu là ICollection<Order>
, cho phép thêm nhiều đối tượng Order
vào một đối tượng Customer
, dựa trên mối quan hệ một-nhiều tồn tại giữa các thực thể Customer
và Order
. Sau đó, đối tượng context cơ sở dữ liệu có kiểu Model1Container được sử dụng để thêm đối tượng Customer vào ngữ cảnh cơ sở dữ liệu. Cuối cùng, việc gọi phương thức SaveChanges()
lưu trữ đối tượng Customer vào cơ sở dữ liệu.
Kết quả:
Enter Customer name: Alex Parker
Enter Customer Address: 10th Park Street, Leo Mount
Enter Order Cost: 575
Customer and Order Information added successfully
Truy vấn Dữ liệu bằng Cách Sử dụng Biểu thức Truy vấn LINQ
LINQ cung cấp một mô hình lập trình nhất quán để tạo cú pháp biểu thức truy vấn chuẩn để truy vấn các loại nguồn dữ liệu khác nhau. Tuy nhiên, các nguồn dữ liệu khác nhau chấp nhận các truy vấn theo định dạng khác nhau. Để giải quyết vấn đề này, LINQ cung cấp các nhà cung cấp LINQ khác nhau, như LINQ to Entities, LINQ to SQL, LINQ to Objects và LINQ to XML. Để tạo và thực thi truy vấn chống lại mô hình khái niệm của Entity Framework, các lập trình viên có thể sử dụng LINQ to Entities.
Trong LINQ to Entities, một lập trình viên tạo ra một truy vấn trả về một tập hợp các thực thể được gán kiểu với số lượng không hoặc nhiều. Để tạo truy vấn, lập trình viên cần một nguồn dữ liệu mà truy vấn sẽ thực thi. Một thể hiện của lớp ObjectQuery
đại diện cho nguồn dữ liệu. Trong LINQ to Entities, một truy vấn được lưu trữ trong một biến. Khi truy vấn được thực thi, trước tiên nó được chuyển đổi thành cây lệnh được biểu diễn tương thích với Entity Framework. Sau đó, Entity Framework thực thi truy vấn chống lại nguồn dữ liệu và trả về kết quả.
Đoạn mã bên dưới tạo và thực thi một truy vấn để lấy các bản ghi của tất cả các thực thể Customer cùng với các thực thể Order tương ứng.
public static void DisplayAllCustomers()
{
using (Model1Container dbContext = new ModelContainer())
{
IQueryable<Customer>query = from c in dbContext.Customers
select c;
Console.WriteLine("Customer Order Information:");
foreach (var cust in query)
{
Console.WriteLine("Customer ID: {0}, Name: {1}, Address: {2}", cust.CustomerId, cust.Name, cust.Address);
foreach (var cst in cust.Orders)
{
Console.WriteLine("Order ID: {0}, Cost: {1}", cst.OrderId, cst.Cost);
}
}
}
}
Trong đoạn mã trên, mệnh đề from
chỉ định nguồn dữ liệu mà dữ liệu cần được truy xuất từ đó. dbContext
là một thể hiện của lớp ngữ cảnh dữ liệu cung cấp quyền truy cập vào nguồn dữ liệu của Customer
, và c là biến phạm vi. Khi truy vấn được thực thi, biến phạm vi hoạt động như một tham chiếu đến từng phần tử kế tiếp trong nguồn dữ liệu. Mệnh đề select
trong truy vấn LINQ chỉ định loại các phần tử được trả về dưới dạng một đối tượng IQueryable<Customer>.
Vòng lặp foreach
lặp qua các kết quả của truy vấn được trả về dưới dạng một đối tượng IQueryable<Customer>
để in chi tiết khách hàng và đơn hàng.
Kết quả:
Customer Order Information:
Customer ID: 1, Name: Alex Parker, Address: 10th Park Street, Leo Mount
Order ID: 1, Cost: 575
Customer ID: 2, Name: Peter Milne, Address: Lake View Street, Cheros Mount
Order ID: 2, Cost: 800
Ngoài việc truy xuất dữ liệu đơn giản, các lập trình viên có thể sử dụng LINQ để thực hiện nhiều hoạt động khác nhau, chẳng hạn như hình thành dự án, lọc dữ liệu và sắp xếp dữ liệu.
- Hình thành Dự án
Khi sử dụng truy vấn LINQ, người lập trình có thể chỉ cần truy xuất các thuộc tính cụ thể của một thực thể từ cơ sở dữ liệu, ví dụ chỉ thuộc tính Name của các thực thể Customer. Người lập trình có thể làm điều này bằng cách hình thành dự án trong mệnh đề select
.
Đoạn mã bên dưới thể hiện một truy vấn LINQ truy xuất chỉ tên khách hàng của các thực thể Customer.
public static void DisplayCustomerNames()
{
using (Model1Container dbContext = new Model1Container())
{
IQueryable<String> query = from c in dbContext.Customers
select c.Name;
Console.WriteLine("Customer Names:");
foreach (String custName in query)
{
Console.WriteLine(custName);
}
}
}
Trong đoạn mã trên, phương thức select
truy xuất một chuỗi tên khách hàng dưới dạng một đối tượng IQueryable<String>
. Vòng lặp foreach
lặp qua kết quả để in ra các tên.
Kết quả:
Customer Names
Alex Parker
Peter Milne
- Lọc dữ liệu
Mệnh đề where
trong truy vấn LINQ cho phép lọc dữ liệu dựa trên một điều kiện Boolean, được gọi là điều kiện predicate. Mệnh đề where
áp dụng điều kiện này cho biến phạm vi đại diện cho các phần tử nguồn và chỉ trả về những phần tử mà điều kiện là đúng. Đoạn mã bên dưới sử dụng mệnh đề where
để lọc các bản ghi khách hàng.
public static void DisplayCustomerByName()
{
using (Model1Container dbContext = new Model1Container())
{
IQueryable<Customer> query = from c in dbContext.Customers
where c.Name == "Alex Parker"
select c;
Console.WriteLine("Customer Information:");
foreach (Customer cust in query)
{
Console.WriteLine("Customer ID: {0}, Name: {1}, Address: {2}",
cust.CustomerId, cust.Name, cust.Address);
}
}
}
Đoạn mã trên sử dụng mệnh đề where
để truy xuất thông tin của khách hàng có tên là Alex Parker
. Câu lệnh foreach
lặp qua kết quả để in thông tin của khách hàng.
Kết quả:
Customer Information:
Customer ID: 1, Name: Alex Parker, Address: 10th Park Street, Leo Mount
Truy vấn Dữ liệu bằng cách sử dụng các Phương thức LINQ
Cho đến nay, các truy vấn LINQ đã được tạo bằng cú pháp biểu thức truy vấn. Những truy vấn như vậy được biên dịch thành các cuộc gọi phương thức tới các toán tử truy vấn tiêu chuẩn, chẳng hạn như select
, where
, và orderby
. Một cách khác để tạo các truy vấn LINQ là sử dụng các phương thức dựa trên phương thức, trong đó các lập trình viên có thể trực tiếp thực hiện các cuộc gọi phương thức tới các toán tử truy vấn tiêu chuẩn, truyền biểu thức lambda làm tham số.
Đoạn mã bên dưới sử dụng phương thức Select
để chiếu thuộc tính Name
và Address
của các thực thể Customer
thành một chuỗi các kiểu ẩn danh.
public static void DisplayPropertiesMethodBasedQuery()
{
using (Model1Container dbContext = new Model1Container())
{
var query = dbContext.Customers.Select(c => new
{
CustomerName = c.Name,
CustomerAddress = c.Address
});
Console.WriteLine("Customer Names and Addresses:");
foreach (var custInfo in query)
{
Console.WriteLine("Name: {0}, Address: {1}", custInfo.CustomerName, custInfo.CustomerAddress);
}
}
}
Kết quả:
Customer Names and Addresses:
Name: AlexParker, Address: 10thPark Street , LeoMount
Name: Peter Milne, Address: Lake View Street , Cheros Mount
Tương tự, một nhà phát triển có thể sử dụng các toán tử khác như Where
, GroupBy
, Max
, và các toán tử khác thông qua các truy vấn dựa trên phương thức.
Đa Luồng và Lập Trình Bất Đồng Bộ
Ứng dụng C# thường phải thực hiện đồng thời nhiều nhiệm vụ. Ví dụ, một ứng dụng C# mô phỏng trò chơi đua xe phải thu thập đầu vào từ người dùng để điều hướng và di chuyển xe đua, đồng thời di chuyển các xe khác trong trò chơi đến đích. Những ứng dụng như vậy, được gọi là ứng dụng đa luồng, sử dụng nhiều luồng để thực thi đồng thời nhiều phần của mã. Trong ngữ cảnh của ngôn ngữ lập trình, một luồng là một luồng điều khiển trong ứng dụng đang thực thi. Một ứng dụng sẽ có ít nhất một luồng được biết đến là luồng chính thực thi ứng dụng. Một lập trình viên có thể tạo ra nhiều luồng khác nhau xuất phát từ luồng chính để xử lý đồng thời các nhiệm vụ của ứng dụng.
Một lập trình viên có thể sử dụng các lớp và giao diện khác nhau trong không gian tên System.Threading
cung cấp hỗ trợ tích hợp cho lập trình đa luồng trong .NET Framework.
Lớp Thread
Lớp Thread
của không gian tên System.Threading
cho phép các lập trình viên tạo và kiểm soát một luồng trong một ứng dụng đa luồng. Mỗi luồng trong một ứng dụng đi qua các trạng thái khác nhau được biểu diễn bằng các thành viên của kiểu dữ liệu ThreadState
. Một luồng mới có thể được khởi tạo bằng cách truyền một ThreadStart
delegate vào constructor của lớp Thread
. ThreadStart
delegate đại diện cho phương thức mà luồng mới sẽ thực thi. Một khi một luồng được khởi tạo, nó có thể được bắt đầu bằng cách gọi phương thức Start()
của lớp Thread
.
Đoạn mã bên dưới khởi tạo và bắt đầu một chuỗi mới.
class ThreadDemo
{
public static void Print()
{
while (true)
Console.Write("1");
}
}
static void Main(string[] args)
{
Thread newThread = new Thread(new ThreadStart(Print));
newThread.Start();
while(true){
Console.Write("2");
}
}
}
Trong đoạn mã trên, phương thức Print()
sử dụng một vòng lặp vô hạn để in giá trị 1 ra màn hình console. Phương thức Main()
khởi tạo và bắt đầu một luồng mới để thực thi phương thức Print()
. Phương thức Main()
, cũng sử dụng một vòng lặp vô hạn để in giá trị 2
ra màn hình console.
Trong chương trình này, hai luồng đồng thời thực thi cả hai vòng lặp vô hạn, dẫn đến kết quả được hiển thị như trong hình bên dưới.
Lớp ThreadPool
Namespace System.Threading
cung cấp lớp ThreadPool
để tạo và chia sẻ nhiều luồng khi ứng dụng cần. Lớp ThreadPool
đại diện cho một bể luồng, tức là một tập hợp các luồng trong ứng dụng. Dựa trên yêu cầu từ ứng dụng, bể luồng gán một luồng để thực hiện một nhiệm vụ. Khi luồng hoàn tất việc thực thi, nó được đưa trở lại bể luồng để tái sử dụng cho yêu cầu khác.
Lớp ThreadPool
chứa phương thức QueueUserWorkItem()
mà một lập trình viên có thể gọi để thực thi một phương thức trong một luồng từ bể luồng. Phương thức này chấp nhận một delegate WaitCallback
nhận Object
làm tham số của nó. Delegate WaitCallback
đại diện cho phương thức cần thực thi trong một luồng riêng biệt từ bể luồng.
Đồng bộ hóa Luồng
Khi nhiều luồng phải chia sẻ dữ liệu, hoạt động của chúng phải được điều phối. Điều này đảm bảo rằng một luồng không thay đổi dữ liệu mà luồng khác đang sử dụng để tránh kết quả không đoán trước được. Ví dụ, xem xét hai luồng trong chương trình C#. Một luồng đọc bản ghi khách hàng từ tệp và luồng khác cố gắng cập nhật bản ghi khách hàng cùng một lúc. Trong tình huống này, luồng đang đọc bản ghi khách hàng có thể không nhận được giá trị đã cập nhật vì luồng khác có thể đang cập nhật bản ghi vào thời điểm đó.
Để tránh tình huống như vậy, C# cho phép lập trình viên điều phối và quản lý hành động của nhiều luồng cùng một lúc bằng cách sử dụng các cơ chế đồng bộ hóa luồng sau:
- Khóa sử dụng từ khóa lock
Khóa là quá trình cho phép chỉ có một luồng thực hiện một khối mã tại một thời điểm. Khối mã mà khóa bảo vệ được gọi là một phần quan trọng.
Khóa có thể được triển khai bằng từ khóa lock. Khi sử dụng từ khóa lock, lập trình viên phải truyền một tham chiếu đối tượng mà một luồng cần có để thực thi phần quan trọng. Ví dụ, để khóa một phần của mã trong một phương thức instance, tham chiếu đến đối tượng hiện tại có thể được truyền vào khóa.
- Sự kiện đồng bộ hóa
Cơ chế khóa được sử dụng để đồng bộ hóa luồng hữu ích để bảo vệ các phần quan trọng của mã khỏi truy cập song song của các luồng. Tuy nhiên, khóa không cho phép giao tiếp giữa các luồng. Để cho phép giao tiếp giữa các luồng trong khi đồng bộ hóa chúng, C# hỗ trợ các sự kiện đồng bộ hóa. Sự kiện đồng bộ hóa là một đối tượng có hai trạng thái: signaled (đã báo hiệu) và un-signaled (chưa báo hiệu). Khi một sự kiện đồng bộ hóa ở trạng thái un-signaled, các luồng có thể bị tạm dừng cho đến khi sự kiện đồng bộ hóa chuyển sang trạng thái signaled.
Lớp AutoResetEvent
của namespace System.Threading
đại diện cho một sự kiện đồng bộ hóa thay đổi tự động từ trạng thái signaled sang un-signaled mỗi khi một luồng trở thành hoạt động. Lớp AutoResetEvent
cung cấp phương thức WaitOne()
tạm ngưng luồng hiện tại khỏi việc thực thi cho đến khi sự kiện đồng bộ hóa đến trạng thái signaled. Phương thức Set()
của lớp AutoResetEvent
thay đổi trạng thái của sự kiện đồng bộ hóa từ un-signaled sang signaled.
Ghi chú: Namespace System.Threading
cũng chứa lớp ManualResetEvent
tương tự như lớp AutoResetEvent
, đại diện cho sự kiện đồng bộ hóa. Tuy nhiên, khác với lớp AutoResetEvent
, một đối tượng của lớp ManualResetEvent
phải được thay đổi thủ công từ trạng thái signaled sang un-signaled bằng cách gọi phương thức Reset()
.
Thư viện Task Parallel
Máy tính hiện đại chứa nhiều CPU. Để tận dụng sức mạnh xử lý mà máy tính với nhiều CPU mang lại, một ứng dụng C# phải thực thi các tác vụ song song trên nhiều CPU. Điều này được gọi là lập trình song song. Để làm cho việc lập trình song song và đồng thời đơn giản hơn, .NET Framework giới thiệu Thư viện Task Parallel (TPL). TPL là một tập hợp các loại và APIs công khai trong các namespace System.Threading
và System.Threading.Tasks
.
Lớp Task
TPL cung cấp lớp Task trong namespace System.Threading.Tasks
đại diện cho một tác vụ không đồng bộ trong chương trình. Lập trình viên có thể sử dụng lớp này để gọi một phương thức không đồng bộ. Để tạo một tác vụ, người lập trình cung cấp một delegate người dùng bao gồm mã mà tác vụ sẽ thực thi.
Delegate có thể là một delegate được đặt tên, như delegate Action, một phương thức vô danh hoặc một biểu thức lambda.
Sau khi tạo một Task
, người lập trình gọi phương thức Start()
để bắt đầu tác vụ. Phương thức này chuyển tác vụ cho trình lập lịch tác vụ để gán luồng thực hiện công việc. Để đảm bảo rằng một tác vụ hoàn thành trước khi luồng chính kết thúc, người lập trình có thể gọi phương thức Wait()
của lớp Task. Để đảm bảo rằng tất cả các tác vụ của một chương trình hoàn thành, người lập trình có thể gọi phương thức WaitAll()
và truyền một mảng các đối tượng Tasks
đã được bắt đầu.
Lớp Task
cũng cung cấp phương thức Run()
để tạo và bắt đầu một tác vụ trong một thao tác duy nhất. Đoạn mã bên dưới tạo và bắt đầu hai tác vụ.
class TaskDemo {
private static void printMessage() {
Console.WriteLine("Executed by a Task");
}
static void Main(string[] args) {
Task task1 = new Task(new Action(printMessage));
task1.Start();
Task task2 = Task.Run(() => printMessage());
task1.Wait();
task2.Wait();
Console.WriteLine("Exiting main method");
}
}
Trong đoạn mã trên, phương thức Main()
tạo một Task có tên là task1
bằng cách sử dụng một delegate Action
, truyền tên của phương thức để thực thi bất đồng bộ. Phương thức Start()
được gọi để khởi đầu tác vụ. Thêm vào đó, phương thức Run()
được sử dụng để tạo và khởi chạy một tác vụ khác có tên là task2
. Cuộc gọi sau đó đến Wait()
đảm bảo cả hai tác vụ đều hoàn thành trước khi phương thức Main()
kết thúc.
Kết quả:
Executed by a Task
Executed by a Task
Exiting main method
Lấy kết quả từ một tác vụ:
Thông thường, một chương trình C# cần kết quả sau khi một tác vụ hoàn thành hoạt động của nó. .NET Framework cung cấp lớp Task<T>
kế thừa từ lớp Task
để cung cấp kết quả của một hoạt động bất đồng bộ. Trong lớp Task<T>
, T
là kiểu dữ liệu của kết quả sẽ được tạo ra. Để truy cập kết quả, gọi thuộc tính Result
của lớp Task<T>
.
Tác vụ tiếp theo
Khi nhiều tác vụ thực thi song song, thường xảy ra một tác vụ được gọi là tiền tác vụ, hoàn thành một hoạt động và sau đó gọi một tác vụ thứ hai, được gọi là tác vụ tiếp theo. Tác vụ tiếp theo có thể được thực hiện bằng cách gọi các phương thức nạp chồng ContinueWith()
của tác vụ tiền tác vụ. Dạng đơn giản nhất của phương thức ContinueWith()
nhận một tham số đại diện cho tác vụ sẽ được thực hiện sau khi tác vụ tiền tác vụ hoàn thành. Phương thức ContinueWith()
trả về tác vụ mới. Một lập trình viên có thể gọi phương thức Wait()
trên tác vụ mới để chờ nó hoàn thành.
Hủy Tác vụ
TPL cung cấp lớp CancellationTokenSource
trong không gian tên System.Threading
để hủy một tác vụ chạy lâu dài. Lớp CancellationTokenSource
có thuộc tính Token
trả về một đối tượng của CancellationToken
struct. Đối tượng này thông báo rằng một tác vụ nên được hủy. Khi tạo một tác vụ có thể bị hủy, đối tượng CancellationToken
phải được truyền vào tác vụ.
CancellationToken
struct cung cấp thuộc tính IsCancellationRequested
trả về true
nếu có yêu cầu hủy. Một tác vụ chạy lâu dài có thể truy vấn thuộc tính IsCancellationRequested
để kiểm tra xem có yêu cầu hủy không, và nếu có, kết thúc hoạt động một cách trơn tru. Yêu cầu hủy có thể được tạo bằng cách gọi phương thức Cancel()
của lớp CancellationTokenSource
. Khi hủy một tác vụ, một lập trình viên có thể gọi phương thức Register()
của CancellationToken
để đăng ký một phương thức gọi lại nhận thông báo khi tác vụ được hủy.
Vòng lặp song song
TPL giới thiệu một lớp Parallel
trong không gian tên System.Threading.Tasks
cung cấp các phương thức để thực hiện tính toán song song của các vòng lặp, chẳng hạn như vòng lặp for
và for each
. Phương thức For()
là một phương thức tĩnh trong lớp Parallel
cho phép thực thi một vòng lặp for
với các lần lặp song song. Do các lần lặp của một vòng lặp sử dụng phương thức For()
là song song, thứ tự của các lần lặp có thể thay đổi mỗi khi phương thức For()
thực thi. Phương thức For()
có một số phiên bản nạp chồng. Phiên bản For()
nạp chồng phổ biến nhất nhận ba tham số theo thứ tự cụ thể sau:
- Một giá trị
int
đại diện cho chỉ số bắt đầu của vòng lặp. - Một giá trị
int
đại diện cho chỉ số kết thúc của vòng lặp. - Một đối tượng delegate
System.Action<Int32>
được gọi một lần cho mỗi lần lặp.
Đoạn mã bên dưới sử dụng một vòng lặp for thông thường và phương thức Parallel.For()
.
static void Main(string[] args)
{
Console.WriteLine("\nUsing traditional for loop:");
for (int i = 0; i <= 10; i++)
{
Console.WriteLine("i = {0} executed by thread with ID {1}", i, Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(100);
}
Console.WriteLine("\nUsing Parallel For:");
Parallel.For(0, 10, i =>
{
Console.WriteLine("i = {0} executed by thread with ID {1}", i, Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(100);
});
}
}
Trong đoạn mã trên, phương thức Main()
trước tiên sử dụng một vòng lặp for thông thường để in định danh của luồng hiện tại lên console. Phương thức Sleep()
được sử dụng để tạm dừng luồng chính trong 1.00 ms cho mỗi lần lặp. Như được thể hiện trong hình bên dưới, phương thức WriteLine()
in ra kết quả theo thứ tự tuần tự vì chỉ có một luồng duy nhất đang thực thi vòng lặp for. Phương thức Main()
sau đó thực hiện cùng một hoạt động sử dụng phương thức Parallel.For()
. Như được thể hiện trong hình bên dưới, nhiều luồng được chỉ định bằng thuộc tính Thread.CurrentThread.ManagedThreadId
thực thi vòng lặp for song song và thứ tự lặp không được sắp xếp.
Hình bên dưới thể hiện một trong những kết quả có thể của đoạn mã bên trên.
Parallel LINQ (PLINQ)
LINQ to Objects ám chỉ việc sử dụng các truy vấn LINQ với các bộ sưu tập có thể liệt kê, ví dụ như List<T>
hoặc mảng. PLINQ là triển khai song song của LINQ to Objects. Trong khi LINQ to Objects truy cập một nguồn dữ liệu có thể liệt kê trong bộ nhớ IEnumerable
hoặc IEnumerable<T>
một cách tuần tự, PLINQ cố gắng truy cập song song vào nguồn dữ liệu dựa trên số lượng bộ xử lý trong máy tính chủ. Để truy cập song song, PLINQ chia nguồn dữ liệu thành các đoạn, sau đó thực thi mỗi đoạn thông qua các luồng riêng biệt một cách song song.
Các lớp ParallelEnumerable
trong không gian tên System.Linq
cung cấp các phương thức thực hiện chức năng của PLINQ.
Đoạn mã bên dưới hiển thị cách sử dụng cả LINQ tuần tự cho đối tượng và PLINQ để truy vấn một mảng.
using System;
using System.Linq;
class Program
{
static void Main(string[] args)
{
string[] arr = new string[] { "Peter", "Sam", "Philip", "Andy", "Philip", "Mary", "John", "Pamela" };
var query = from string name in arr select name;
Console.WriteLine("Names retrieved using sequential LINQ");
foreach (var n in query)
{
Console.WriteLine(n);
}
var plinqQuery = from string name in arr.AsParallel() select name;
Console.WriteLine("\nNames retrieved using PLINQ");
foreach (var n in plinqQuery)
{
Console.WriteLine(n);
}
}
}
Đoạn mã trên tạo một mảng chuỗi được khởi tạo với các giá trị. Một truy vấn LINQ tuần tự được sử dụng để lấy các giá trị từ mảng và in chúng ra bảng điều khiển trong một vòng lặp foreach
. Truy vấn thứ hai là một truy vấn PLINQ sử dụng phương thức AsParallel()
trong mệnh đề from
. Truy vấn PLINQ này cũng thực hiện các thao tác giống như truy vấn LINQ tuần tự. Tuy nhiên, do truy vấn PLINQ được thực thi song song, thứ tự các phần tử lấy từ mảng nguồn sẽ khác nhau.
Kết quả:
Names retrieved using sequential LINQ
Peter
Sam
Philip
Andy
Philip
Mary
John
Pamela
Names retrieved using PLINQ
Peter
Philip
Sam
Mary
Philip
John
Andy
Pamela
Collection Đồng bộ (Concurrent Collections)
Các lớp bộ sưu tập trong namespace System.Collections.Generic
cung cấp tính an toàn và hiệu suất tốt hơn so với các lớp bộ sưu tập trong namespace System.Collections
. Tuy nhiên, các bộ sưu tập trong namespace System.Collections.Generic
không an toàn đối với luồng. Do đó, người lập trình phải cung cấp mã đồng bộ hóa luồng để đảm bảo tính toàn vẹn của dữ liệu được lưu trữ trong các bộ sưu tập. Để giải quyết các vấn đề an toàn đối với luồng trong các bộ sưu tập, .NET Framework cung cấp các lớp bộ sưu tập đồng bộ trong namespace System.Collections.Concurrent
. Những lớp này, an toàn đối với luồng, giúp giảm bớt áp lực cho người lập trình khi nhiều luồng truy cập song song vào các bộ sưu tập này. Các lớp quan trọng trong namespace System.Collections.Concurrent
là:
- ConcurrentDictionary<TKey, TValue>
Triển khai an toàn đối với luồng của từ điển các cặp khóa-giá trị.
- ConcurrentQueue<T>
Là một triển khai hàng đợi an toàn theo luồng.
- ConcurrentStack<T>
Là cách triển khai ngăn xếp an toàn theo luồng.
- ConcurrentBag<T>
Là một triển khai an toàn theo luồng của một tập hợp các phần tử không có thứ tự.
Đoạn mã bên dưới sử dụng nhiều luồng để thêm các phần tử vào một đối tượng của lớp sau: ConcurrentDictionary<string, int>
using System;
using System.Collections.Concurrent;
using System.Threading;
class CollectionDemo
{
static ConcurrentDictionary<string, int> dictionary = new ConcurrentDictionary<string, int>();
static void AddToDictionary()
{
for (int i = 0; i < 100; i++)
{
dictionary.TryAdd(i.ToString(), i);
}
}
static void Main(string[] args)
{
Thread thread1 = new Thread(new ThreadStart(AddToDictionary));
Thread thread2 = new Thread(new ThreadStart(AddToDictionary));
thread1.Start();
thread2.Start();
thread1.Join();
thread2.Join();
Console.WriteLine("Total elements in dictionary: {0}", dictionary.Count);
}
}
Đoạn mã trên gọi phương thức TryAdd()
của lớp ConcurrentDictionary
để đồng thời thêm các phần tử vào một đối tượng ConcurrentDictionary<string, int>
bằng cách sử dụng hai luồng riêng biệt. Phương thức TryAdd()
, khác với phương thức Add()
của lớp Dictionary, không ném ra một ngoại lệ nếu một khóa đã tồn tại. Thay vào đó, phương thức TryAdd()
trả về false nếu một khóa đã tồn tại và cho phép chương trình thoát một cách bình thường, như được thể hiện trong hình bên dưới.
Các Phương Pháp Bất Đồng Bộ
TPL cung cấp hỗ trợ cho lập trình bất đồng bộ thông qua hai từ khóa mới: async
và await
. Những từ khóa này có thể được sử dụng để gọi các phương thức chạy lâu trong chương trình một cách bất đồng bộ. Một phương thức được đánh dấu với từ khóa async
thông báo cho trình biên dịch rằng phương thức đó sẽ chứa ít nhất một từ khóa await
. Nếu trình biên dịch nhận thấy một phương thức được đánh dấu là async
nhưng không có từ khóa await
, nó sẽ báo lỗi biên dịch.
Từ khóa await
được áp dụng cho một hoạt động để tạm dừng thực thi của phương thức async
cho đến khi hoạt động đó hoàn tất. Trong thời gian chờ đợi, quyền điều khiển được trả về cho người gọi của phương thức async
. Khi hoạt động được đánh dấu với await
hoàn tất, việc thực thi tiếp tục trong phương thức async
.
Một phương thức được đánh dấu với từ khóa async
có thể có một trong các loại trả về sau:
void
Task
Task<TResult>
Chú ý: Theo quy ước, một phương thức được đánh dấu với từ khóa async
kết thúc bằng hậu tố Async
.
Đoạn mã bên dưới thể hiện việc sử dụng các từ khóa async
và await
.
using System;
using System.Threading;
using System.Threading.Tasks;
class AsynchWaitDemo
{
static async void PerformComputationAsync()
{
Console.WriteLine("Entering asynchronous method");
int result = await new ComplexTask().AnalyzeData();
Console.WriteLine(result.ToString());
}
static void Main(string[] args)
{
PerformComputationAsync();
Console.WriteLine("Main thread executing.");
Console.ReadLine();
}
}
class ComplexTask
{
public Task<int> AnalyzeData()
{
Task<int> task = new Task<int>(GetResult);
task.Start();
return task;
}
public int GetResult()
{
/* Pause Thread to Simulate time-consuming operation */
Thread.Sleep(2000);
return new Random().Next(1, 1000);
}
}
Trong đoạn mã trên, phương thức AnalyzeData()
của lớp ComplexTask
tạo và bắt đầu một công việc mới để thực thi phương thức GetResult()
. Phương thức GetResult()
mô phỏng một hoạt động chạy lâu bằng cách làm cho luồng ngủ trong hai giây trước khi trả về một số ngẫu nhiên. Trong lớp AsynchWaitDemo
, phương thức PerformComputationAsync
() được đánh dấu với từ khóa async
. Phương thức này sử dụng từ khóa await
để đợi phương thức AnalyzeData()
trả về. Trong lúc đợi phương thức AnalyzeData()
trả về, quyền điều khiển được trả về cho phương thức gọi Main()
mà in thông điệp ‘Main thread executing
‘ lên bảng điều khiển. Khi phương thức AnalyzeData()
trả về, thực thi tiếp tục trong phương thức PerformComputationAsync
() và số ngẫu nhiên được trả về được in lên bảng điều khiển. Hình bên dưới cho thấy đầu ra.
Dynamic Programming
C# cung cấp các kiểu dynamic để hỗ trợ lập trình động cho ứng dụng tích hợp .NET với các ngôn ngữ động, như IronPython và các Component Object Model (COM) như API Office Automation APIs.
Trình biên dịch C# không thực hiện kiểm tra kiểu tĩnh trên các đối tượng của loại động. Loại của một đối tượng động được giải quyết vào thời gian chạy bằng cách sử dụng DLR. Một người lập trình sử dụng một loại động không cần phải xác định nguồn của giá trị đối tượng trong quá trình phát triển ứng dụng. Tuy nhiên, bất kỳ lỗi nào không được kiểm tra trong quá trình biên dịch đều gây ra ngoại lệ thời gian chạy.
Để hiểu cách loại động vượt qua kiểm tra kiểu tĩnh, hãy xem đoạn mã bên dưới
using System;
class DemoClass
{
public void Operation(string name)
{
Console.WriteLine("Hello {0}", name);
}
}
class DynamicDemo
{
static void Main(string[] args)
{
dynamic dynaObj = new DemoClass();
dynaObj.Operation();
}
}
Trong đoạn mã trên, lớp DemoClass có một phương thức Operation() duy nhất nhận một tham số String
. Phương thức Main()
trong lớp DynamicDemo tạo một kiểu động và gán một đối tượng DemoClass cho nó. Kiểu động sau đó gọi phương thức Operation() mà không truyền bất kỳ tham số nào.
Tuy nhiên, chương trình biên dịch mà không có bất kỳ lỗi nào vì trình biên dịch khi gặp từ khóa dynamic
không thực hiện bất kỳ kiểm tra kiểu nào. Tuy nhiên, khi thực thi chương trình, một ngoại lệ thời gian chạy sẽ được ném ra, như được thể hiện trong hình bên dưới.
Từ khóa dynamic
cũng có thể được áp dụng cho các trường (fields), thuộc tính (properties), tham số phương thức (method parameters) và kiểu trả về (return types).
Đoạn mã tiếp theo bên dưới cho thấy cách từ khóa dynamic
có thể được áp dụng vào các phương thức để làm cho chúng có thể tái sử dụng trong một chương trình.
using System;
class DynamicDemo
{
static dynamic DynaMethod(dynamic param)
{
if (param is int)
{
Console.WriteLine("Dynamic parameter of type int has value {0}", param);
return param;
}
else if (param is string)
{
Console.WriteLine("Dynamic parameter of type string has value {0}", param);
return param;
}
else
{
Console.WriteLine("Dynamic parameter of unknown type has value {0}", param);
return param;
}
}
static void Main(string[] args)
{
dynamic dynaVar1 = DynaMethod(3);
dynamic dynaVar2 = DynaMethod("HelloWorld");
dynamic dynaVar3 = DynaMethod(12.5);
Console.WriteLine("\nReturned dynamic values:\n{0}\n{1}\n{2}", dynaVar1, dynaVar2, dynaVar3);
}
}
Trong đoạn mã trên, phương thức DynaMethod()
chấp nhận một loại dữ liệu dynamic làm tham số. Bên trong phương thức DynaMethod()
, từ khóa ‘if-else
‘ được sử dụng để kiểm tra kiểu dữ liệu của tham số và trả về giá trị tương ứng. Vì kiểu trả về của phương thức DynaMethod()
cũng là dynamic, không có ràng buộc về kiểu dữ liệu mà phương thức có thể trả về. Phương thức Main()
gọi phương thức DynaMethod()
với các giá trị số nguyên, chuỗi và số thập phân và in các giá trị trả về lên màn hình console.
Kết quả:
Dynamic parameter of type int has value 3
Dynamic parameter of type string has value HelloWorld
Dynamic parameter of unknown type has value 12.5
Returned dynamic values
3
HelloWorld
12.5