Tải bản đầy đủ (.pdf) (31 trang)

4 chuong 04 tủ tài liệu bách khoa

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (1.95 MB, 31 trang )

Chương 4 Một số tính năng quan trọng
trong C#
C# là ngôn ngữ nhiều tính năng và nhiều người lập trình vẫn chưa thông thạo hết
những tính năng mà trong sách này đề cập đến. Trong chương này chúng ta sẽ mô tả lại
một số tính năng hữu ích của MVC mà một người lập trình ứng dụng MVC cần biết thông
qua những ví dụ được đưa ra trong sách
Nội dung sẽ chỉ gồm những tổng quan ngắn gọn và cơ bản về mỗi tính năng, nếu
nhưng người đọc muốn đi sâu hơn vào ngôn ngữ C# hoặc LINQ có thể tìm xem qua : For a
complete guide to C#, Introducing Visual C#, và để nghiên cứu sâu hơn về LINQ, tìm đọc
Pro LINQ in C#. Những ví dụ chi tiết về .NET hỗ trợ lập trình không đồng bộ (asynchronous
programming) , tìm đọc Pro .net parallel Programming in C#. Tất cả sách đều được phát
hành bởi Apress.

Chuẩn bị Project cho ví dụ
Để tiến hành mô tả những tính năng của ngôn ngữ lập trình trong chương này, chúng
ta tạo một project tên LanguageFeatures sử dụng template ASP.NET MVC Web application.
Chọn Emty project cho phần nội dung lúc đầu và chọn mục MVC để dựng sẵn các folders và
những phần liên quan (Xem chương 2). Những tính năng ngôn ngữ chúng ta vừa miêu tả
không dành riêng cho MVC, nhưng VS 2013 Express cho Web không hỗ trợ tạo project có
thể viết trên console vì thế bạn nên tạo một ứng dụng MVC nếu muốn theo kịp những ví dụ.
Chúng ta cần một controller cơ bản để diễn tả tính năng trong ngôn ngữ lập trình, do đó
chúng tạ tạo một lớp HomeController.cs trong folder Controllers. Các làm: nhấp chuột phải tại
folder Controllers trong cửa sổ Solution explorer, chọn Add >Controller. Trong phần pop-up
menu, chọn MVC 5 Controller-Emty trong phần Add Scafold menu , sau đó nhấn nút Add.
Đặt tên lớp là HomeController trong hộp thoại Add Controller và nhấn nút Add để tạo lớp
Controller. List 4-1


Chúng ta sẽ tạo một action method đơn giản cho mỗi ví dụ, kết quả nhận được của
Index action method là một thông điệp đơn giản
Chú ý: Lớp HomeController sẽ không biên dịch ở thời điểm hiện tại vì nó khai báo


namespace LanguageFeatures.Models. Namespace này sẽ không được tạo cho đến khi
chúng ta thêm một lớp vào folder Models.Chúng ta sẽ làm việc này trong ví dụ đầu tiên của
phần tiếp theo.
Để hiển thị kết quả của action method. Chúng ta chuột phải vào tên Index của action
method> chọn Add View và tạo một view đặt tên là result. (list 4-2)(Không quan trọng chúng
ta chọn option nào trong hộp thoại Add View vì chúng ta sẽ thay thế nội dung ban đầu của
file với đoạn code trong list 4-2)

Chúng ta có thể thấy đây là một strongly typed view , khi model type là kiểu String ở
hầu hết các phần. Ví dụ sau đây không phải là một ví dụ phức tạp và chúng ta có thể biểu
diễn kết quả bằng một đoạn string đơn giản


Thêm System.Net.Http Assembly
Trong phần sau của chương này, chúng ta sẽ sử dụng ví dụ dựa vào System.Net.Http
assembly. Tính năng này mặc định không được thêm vào MVC. Chọn Add Reference trong
Visual Studio Project menu để mở cửa sổ Reference Manager. Đảm bảo rằng chúng ta
đang chọn section Assemblies trong danh sách bên trái sau đó nhấn chọn System.Net.Http
(Hình 4-1)


Sử dụng tính năng Automatically Implemented Properties
Tính năng property cơ bản trong C# cho phép người dùng triển khai 1 phần của dữ
liệu bằng cách tách cách dữ liệu được đặt và lấy về. (Listing 4-3) có chứa một ví dụ cơ bản
của lớp tên Product. Chung ta thêm lớp này vào folder Models trong project của mình với
tên gọi Product.cs

Thuộc tính Name được in đậm. Câu lệnh get (hoặc còn gọi là getter) thực hiện khi dữ
liệu được đọc và câu lệnh set (hoặc còn gọi là settle) được thực hiện khi một giá trị được
điền vào cho thuộc tính (biến được biện value thể hiện cho giá trị được điền vào). Một thuộc

tính được sử dụng bởi các lớp khác và xem nó như một field. Hình 4-4 cho thấy
AutoProperty action method chúng ta sẽ thêm vào trong lớp Homecontroller


Chúng ta có thể thấy giá trị của thuộc tính được đọc và đặt giá trị như những field bình
thường. Sử dụng properties được ưa chuộng hơn so với fields bởi vì nó có thể thay đổi câu
lệnh trong phần code get và set mà không cần thay đổi lớp phụ thuộc của thuộc tính
Mẹo Chúng ta để ý ràng chúng ta đã đặt đối số thứ hai vào View như một đối tượng
(listing 4-4). Điều này là bởi vì View method phần tham số cho phép chứa 2 đối số dạng
string và khác so với phần tham số chỉ cho phép một chuỗi và một đối tượng. Để tránh gọi
nhầm, chúng ta sẽ khai báo tường minh tham số thứ hai. Chúng ta sẽ đề cập đến phần
tham số cho view Method ở chương 20
Chúng ta có thể kiểm tra hiệu quả bằng cách khởi động project hướng đến địa chỉ
/Home/AutoProperty (nhắm đến AutoProperty action method và sẽ là phần nền để kiểm thử
mỗi ví dụng trong chương này). Bởi vì chúng ta chưa truyền một chuỗi string vào từ action
method đến view. Đây là kết quả dưới dạng text, thay vì hình chụp. Đây là kết quả của
action method trong listing 4-4

Tất cả thuộc tính đều tốt đẹp, tuy nhiên chúng trở nên khó khăn khi chúng ta có một
class chứa quá nhiều thuộc tính, tất cả chúng đều được đưa vào một field, tạo nên file class
rườm rà một cách không cần thiết. Ví dụ 4-5 cho thấy thêm một số thuộc tính vào lớp
Product trong file Product.cs


Chúng ta muốn có được sự mềm dẻo trong mỗi thuộc tính mà không cần cấp số nhân
lượng getter và setter. Giải pháp là tự động thực hiện thao tác property, còn được gọi là
automatic property. Với tính năng này chúng ta có thể tạo các đoạn code cho sẵn của thuộc
tính mà không cần đặt lại các đoạn code getter và setter . Hình 4-6



Chú ý rằng chúng ta không định nghĩa phần thân của getter và setter đặt cho thuộc
tính đó. Cả hai thuộc tính đều được thược hiện cho người dùng thông qua hệ thống biên
dịch của C#. Sử dụng tính năng thuộc tính tự động này không khác biệt so với việc viết
gettter và setter cho thuộc tính thông thương. Đoạn code trong list 4-4 sẽ hoạt động mà
không cần điều chỉnh
Bằng cách sử dụng thuộc tính tự động, chúng ta rút ngắn được thời gian nhập code,
tạo ra các đoạn code dễ đọc nhưng đồng thời vẫn hỗ trợ tính mềm dẻo mà thuộc tính cung
cấp. Nếu một lúc nào đó chúng ta thay đổi cách vận hành thuộc tính, chúng ta vẫn có thể
quay lại cách thiết lập thuộc tính truyền thống. Như trong list 4-7, chúng ta cần phải chỉnh
sửa khi cần thay đổi cách thức thuộc tính Name được thiết lập

Chú ý: chúng ta phải chỉnh cả getter lẫn setter để trả về thuộc tính thông thường. C#
không hỗ trợ việc trộn lẫn chế độ tự động và nhập liệu thông thường của getter và setter
trên cùng một thuộc tính


Sử dụng Object và Collection Initializers
Một công việc buồn chán trong quá trình lập trình là cấu trúc một đối tượng mới và
đưa vào các giá trị cho thuộc tính. List 4-8 miêu tả hành động thêm vào của CreatProduct
action method trong Home controller

Chúng ta đi qua ba bước để tạo một đối tượng Product và tạo ra một kết quả: tạo đối
tượng, đặt các giá trị tham số và sau đó gọi đến View method để trả về kết quả thông qua
view. May mắn thay, chúng ta có thể sử dụng tính năng object initializer, cho phép chúng ta
tạo và biểu diễn một instance của đối tượng Product trong một bước đơn giản . List 4-9


Trong dấu ({ }) sau lời gọi Product là một mẫu của Initializer cho phép chúng ta cung
cấp giá trị vào các thông số như một phần của quá trình cấu trúc đối tượng. Tính năng
tương tự cho phép chúng ta khởi tạo nội dung của một bộ và một danh sách như một phần

của quá trình cấu trúc. List 4-10

Đoạn code trên miệu tả cách cấu trúc và khởi tạo một dãy và 2 lớp từ thư viện
collection. Tính năng này là một cú pháp hiệu quả. Giúp cho C# trở nên thân thiện hơn mà
không gây ra thêm tác động hay đem lại những lợi ích nào khác


Sử dụng Extension Methods
Extension Method thêm các method vào các class mà chúng ta không sở hữu hoặc
không thể điều chỉnh trực tiếp một cách thuận lợi.List 4-11 biểu diễn 1 lớp ShoppingCart,
chúng ta sẽ thêm vào folder Models trong file tên là ShoppingCart.cs và sẽ thể hiện một bộ
các đối tượng Product

Đây là một lớp cơ bản hoạt động như một gói danh sách của các đối tượng Product
(Đây là một lớp đơn giản phục vụ cho ví vụ). Gỉa sử chúng ta cần xác định tổng giá trị của
các đối tượng Product trong lớp ShoppingCart nhưng không để chỉnh sửa lớp đó trực tiếp, có
thể nó đóng vai trò như bên thứ 3 khi chúng ta không nắm giữ phần source code . Chúng ta
có thể thêm một extension method để thêm tính năng mà chúng ta cần. List 4-12 biểu diễn
lớp MyExtensionMethods chúng ta thêm vào trong folder Models trong file
MyExtensionMethods.cs

Keyword “this” đứng trước tham số của TotalPrices đánh dấu nó là một extension
method. Tham số đầu tiên cho .NET biết extension method này có thể được áp dụng vào
lớp ShoppingCart trong trường hợp này. Chúng ta có thể tham khảo instance của
ShoppingCart mà extension method này áp dụng để sử dụng tham số cartParam. Method của
chúng ta liệt kệ các đối tượng Products trong ShoppingCart sau đó trả về tống giá của các
Product (dựa vào thuộc tính Product.Price). List 4-13 cho thấy cách chúng ta thêm một
extension method trong một action method tên UseExtension mà chúng ta thêm trong trong
Homecontroller



Lưu ý : Extension Method không cho phép chúng ta bỏ qua quy tắc truy suất là các class
định nghĩa method và thuộc tính của chính nó. Chúng ta có thể mở rộng chức năng của
một class bằng cách sử dụng extension method nhưng chỉ sử dụng thành phần của lớp
chúng ta truy suất vào đó

Chúng ta gọi TotalPrice method trong đối tượng ShoppingCart như nó là một phần của
lớp ShoppingCart mặc dù nó chỉ là phần mở rộng được định nghĩa bởi 1 lớp khac. .NET sẽ
tìm phần mở rộng của các class nếu chúng có tham chiếu từ lớp hiện tại, nghĩa là chúng là
một phần của cùng 1 namespace hoặc trong namespace có sử dụng câu lệnh using. Đây là
một ví dụ trong UseExtension action methos, chúng ta có thể thấy ứng dụng chuyển đến địa
chỉ URL /Home/UseExtension


Ứng dụng Extension Method vào Interface
Chúng ta cũng có thể tạo các extension method và áp dụng nó vào một interface. Điều
này cho phép chúng ta gọi extension method từ tất cả các class thực thi lớp interface đó.
List 4-14 biểu diễn lớp ShoppingCart đã được thay đổi để thực thi lớp interface
IEnumerable<Product>

Bây giờ chúng ta có thể thêm extension method vào lớp interface
IEnumarable<Product>. List 4-15

Kiểu của tham số đầu tiên đã thay bằng IEnumerable<Product> nghĩa là vòng lặp
foreach trong phần thân làm việc trực tiếp trên các đối tượng Product. Việc chuyển qua
interface nghĩa là chúng ta có thể tính tổng giá trị của đối tượng Product được liệt kệ bởi bất
kỳ IEnumerable<Product>, bao gồm cả ShoppingCart là một thể hiện của nó, đồng thời cũng là
danh sách các đối tượng Product. List 4-16




Ghi nhớ: cách C# thực hiện lập danh sách theo interface IEnumerable<T> có một chút
khác thường. Chúng ta có thể xem nội dung của các interfaces trong tài liệu MSDN. Công
cụ hỗ trợ này được xử lý bởi trình biên dịch vì thế nên phần code của phần C# trước đây
vẫn hoạt động. Chúng ta có thể sử dụng một kiểu lớp collection khác cho ví dụ này nhưng ví
dụ này thể hiện được những mảng tối của C#
Khi chúng ta chạy lại Project, và chỉ vào action method, chúng ta sẽ nhận được cùng
một kết quả khi thu thập các đối tượng Product

Tạo bộ lọc Extension Methods (Extension Methods Filter)
Vấn đề cuối cùng cần đề cập về extension methods là nó có thể dùng để lọc một bộ
các đối tượng. Một extension method chạy theo IEnumerable<T> và cũng trả về
IEnumerable<T> có thể dùng từ khóa yield để áp dụng các tiêu chí cho các item trong source
data để trả về 1 bộ các kết quả. List 4-17 mô tả một method như vậy, nó được thêm vào lớp
MyExtensionMethods

Extension method này gọi là FilterByCategory, lấy thêm một tham số nữa cho phép
chúng ta truyền vào điều kiện lọc khi chúng ta có lời gọi đến method này. Các đối tượng
Product có thuộc tính Category thỏa mãn tham số này sẽ được trả về trong phần kết quả của
IEnumerable<Product>, những đối tượng không thỏa mãn sẽ bị lược bỏ. List 4-18 miêu tả
cách sử dụng method này


Khi chúng ta có lời gọi hàm FilterByCategory trong lớp ShoppingCart, chỉ những sản
phẩm có thuộc tính Category là Soccer được trả về. Nếu chúng ta cho chạy project và điều
hướng vào UseFileterExtensionMethod action method, chúng ta có thể thấy kết quả là tổng giá
trị của các đối tượng có thuộc tính Category là Soccer


Sử dụng Lambda Expression

Chúng ta có thể sử dụng một đại diện để giúp cho FilterByCategory có tính tổng thể
hơn. Bằng cách này, đại diện sẽ được gọi đối với từng Product để lọc các đối tượng mỗi khi
chúng ta chọn. List 4-19 miêu tả Filter extension method được thêm vào lớp
MyExtensionMethods

Chúng ta sử dụng Func như là tham số để lọc, nghĩa là chúng ta không cần định nghĩa
đại diện này như một kiểu. Đại diện này lấy tham số là Product và trả về kết quả kiểu bool,
nghĩa là sẽ trả về true nếu đối tượng Product đó có nằm trong phần kết quả trả về. Phần còn
lại của cách sắp xếp này tương đối dài dòn. List 4-20 miêu tả những thay đổi trên
UserFilterExtensionMethod action method trong lớp Home controller


Chúng ta đã tiến thêm một bước, hiện chúng ta có thể lọc các đối tượng Product sử
dụng những tiêu chí được đặt ra trong phần đại diện nhưng chúng ta phải định nghĩa Func
cho mỗi kiểu lọc chúng ta muốn, đây không phải là ý tưởng chúng ta hướng đến. Một cách
để giảm bớt sự dài dòng này là sử dụng lambda expression, đây là một định dạng xúc tích
nhừng thể hiện phần thân của một method trong bằng một đại diện. Chúng ta có thể dùng
nó để định nghĩa đại diện trong action method: List 4-21


Phần lambda expression được tô đậm. Tham số được biểu diễn mà không cần định
nghĩa kiểu, nghĩa là nó sẽ được suy ra một cách tự động. Phần ký tự ‘=>’ có thể hiểu nghĩa
là đi đến và sau đó là phần link chỉ đến tham số cho kết quả của lambda expression. Trong
ví dụ này, một tham số Product gọi là prod trả về kết quả kiểu bool, nghĩa là sẽ trả về kết quả
true nếu tham số Category của prod bằng Soccer. Chúng ta có thể xây dựng các câu lệnh
chặt chẽ hơn nữa bằng cách bỏ đi hoàn toàn từ khóa Func. List 4-22


Trong ví dụ này chúng ta cung cấp lambda expression như một tham số cho hàm
Filter. Đây là một cách hay và tự nhiên để thể hiện cách lọc mà chúng ta muốn áp dụng.

Chúng ta có thể kết hợp nhiều bộ lọc bằng cách mở rộng kết quả trong lambda expression
như trong List 4-23


Khi thực hiện, chương trình sẽ kiểm tra xem các đối tượng Product nào có thuộc tính
Category là Soccer và có thuộc tính giá Price lớn hơn 20
Một số mẫu cho Lambda expression
Chúng ta không cần định nghĩa logic cho các đại diện trong lambda expreesion, chúng
ta có thể dễ dàng gọi đến một method như sau

Nếu chúng ta cần lambda expression cho một đại diện có nhiều tham số, chúng ta có
thể nhóm các tham số vào trong dấu ngoặc :

Và cuối cùng khi chúng ta cần hàm logic trong lambda expression cần nhiều hơn một
câu lệnh, chúng ta có thể dùng dấu ({ }) và hoàn tất với câu lệnh return

Chúng ta không nhất thiết sử dụng Lambda expression trong phần code của mình tuy
nhiên chúng là một công cụ khéo léo xử lý các hàm phức tạp một cách tường minh và dễ
đọc. Công cụ này sẽ được sử dụng xuyên suốt trong cuốn sách này


Sử dụng Automatic Type Interface
Từ khóa var trong C# cho phép chúng ta định nghĩa biến mà không cần định nghĩa
kiểu của biến đó một cách rõ ràng. List 4.24

Nhưng không có nghĩa biến myVariable không có kiểu, chúng ta chỉ yêu cầu trình biên
dịch suy ra từ trong phần code. Chúng ta có thể thấy trong câu lệnh, trình biên dịch sẽ cho
phép các thành phần trong lớp liên quan (trong trường hợp này là Product) được gọi

Sử dụng kiểu ẩn danh (Anoymous Types)

Bằng cách kết hợp các khởi tạo của đối tượng và các kiểu suy ra, chúng ta chó thể
tạo một kho dữ liệu của các đối tượng mà không cần định nghĩa lớp hoặc kiến trúc tương
ứng


Trong cí dụ này, myAnonType là một đối tượng có kiểu ẩn danh. Điều này không có
nghĩa là nó là dạng dynamic như trong định nghĩa của JavaScript về các biến dynamic. Điều
này mang nghĩa là định nghĩa kiểu của nó sẽ được tạo một cách tự động bởi trình biên dịch.
Điều kiện về kiểu mạnh vẫn được áp dụng
Ví dụ : Chúng ta chỉ có thể get và set các thuộc tính được định nghĩa trong phần khởi
tạo
Trình biên dịch C# sinh ra class dựa theo tên và kiểu từ các tham số trong phần khởi
tạo. Hai đối tượng có kiểu ẩn danh có tên thuộc tính và kiểu thuộc tính giống nhau sẽ được
tự động tạo thành cùng 1 class. Có nghĩa là chúng ta có thể tạo ra một dãy các các đối
tượng có kiểu ẩn danh như trong List 4-26. CreateAnonArray action method được thêm vào
trong lớp Home controller


Chú ý rằng chúng ta dùng var để định nghĩa dãy các biến. Chúng ta phải làm như thế
vì chúng ta vẫn chưa định nghĩa kiểu so với định nghĩa kiểu cho các dãy thông thường. Mặc
dù chúng ta chưa xác định một lớp cho các đối tượng này, chúng ta vẫn có thể liệt kê các
nội dung của dãy và đọc giá trị của thuộc tính Name từ mỗi đối tượng. Điều này là quan
trọng vì nếu không có tính năng này chúng ta không thể tạo ra một dãy các đối tượng có
kiểu ẩn danh hoặc chúng ta có thể tạo ra một dãy nhưng chúng ta không thể thao tác gì trên
đó. Chúng ta sẽ thấy kết quả khi chúng ta cho chạy ví dụ và điều hướng đến action method
tương ứng


Thực hiện các câu lệnh truy vấn tích hợp trong ngôn ngữ
Tất cả các tính năng được đề cập cho đến giờ đều có thể sử dụng tốt trong LINQ.

LINQ là một phần tuyệt vời và hấp dẫn được thêm vào .NET. Nếu các bạn chưa từng sử
dụng LINQ, các bạn đã bỏ lỡ nhìêu thứ. LINQ là một bộ truy vấn dữ liệu có cấu trúc giống
với SQL. Tưởng tượng chúng ta có một nhóm các đối tượng Product và chúng ta muốn tìm
ra ba sản phẩm với giá cao nhất và truyền nó đến View method. Nếu không sử dụng LINQ,
chúng ta sẽ phải thực hiện như trong Listing 4-27(thêm FindProduct action method vào Home
controller)

Với LINQ, chúng ta tối giản đáng kể quá trình truy vấn (Listing 4-18)


Cách làm này khéo léo hơn rất nhiều. Chúng ta có thể thấy câu truy vấn dạng SQL
được tô đậm. Chúng ta sắp xếp đối tượng Product theo thứ tự giảm dần và dùng câu lệnh
select để lấy được giá trị kiểu ẩn danh chỉ bao gồm thuộc tính Name và Price. Phong cách
LINQ này được gọi là query syntax và đây là kiểu mà các nhà phát triển cảm thấy thoải mái
nhất khi họ bắt đầu sử dụng LINQ. Một điểm chưa tốt trong câu truy vấn này là nó chỉ trả về
kiểu ẩn danh cho mỗi đối tượng Product trong dãy khi ta dùng câu lệnh truy vấn. Vì thế
chúng ta cũng cần thao tác trên kết quả trả về để nhận được ba giá trị đầu tiên và in thông
tin chi tiết của chúng ra.
Tuy nhiên nếu bạn sẵn sàng từ bỏ tính đơn giảm của query syntax. Chúng ta có thể
nhận được nhiều điểm mạnh khi dùng LINQ. . Một trong số những hướng thay thế là phong
cách dot-notation syntax hoặc ngắn gọn là dot-notation dựa trên extension method. List 4-29
miêu tả cách làm thay thế này khi dùng trên đối tượng Product


Câu truy vấn LINQ được tô đậm, tuy nhiên không dễ nhìn hơn so với query syntax bởi
vì không phải tính năng nào của LINQ cũng dựa trên từ khóa của C#. Cho những câu truy
vấn LINQ thực sự, chúng ta cần sử dụng extension methods. Mỗi LINQ extension methods
trong danh sách đựng áp dụng vào IEnumerable<T> và cũng trả về kết quả IEnumerabler<T>.
Cho phép chúng ta liên kết các method với nhau trong câu truy vấn phức tạp
Lưu ý: Tất cả các LINQ extension methods đều lấy namespace System.linq. Nghĩa là

chúng ta phải sử dụng câu lệnh using khi muốn sử dụng truy vấn. Visual Studio thêm
namespace System.linq vào các lớp controller một cách tự động tuy nhiên ở những phần
khác trong project MVC, chúng ta phải thêm vào bằng thao tác thủ công
OrderByDescending method sắp xếp lại các item trong dữ liệu gốc, lambda expression
trả về giá trị mà chúng ta dùng để so sánh. Take method trả về một số cụ thể của item trong
kết quả trả về (không thể dùng query syntax). Select method cho phép chúng ta xây dựng

kết quả trả về, định dạng kiến trúc mà chúng ta muốn. Trong trường hợp này, chúng ta xây
dựng một đối tượng ẩn danh bao gồm thuộc tính Name và Price
Mẹo: chú ý rằng chúng ta không cần định dạng tên cho thuộc tính của kiểu ẩn danh.
C# suy ra nó từ thuộc tính chúng ta chọn trong select method
Bảng 4-2 mô tả một số LINQ extension method hữu dụng. Chúng ta sẽ sử dụng LINQ
xuyên suốt phần còn lại của sách. Bảng này sẽ rất hữu dụng khi bắt gặp những extension
method mà bạn chưa từng gặp trước đây. Tất cả các LINQ method trong bảng đều hoạt
động trong IEnumerable<T>


×