Expression Trees
Chào mọi người, hôm nay chúng ta sẽ nói về Expression Trees. Đây là một phần tiếp theo sau video về reflection vì chủ đề này nằm trong cùng một lĩnh vực về mã của bạn hoạt động như dữ liệu mà bạn sẽ thao tác. Vậy nếu bạn chưa xem video về reflection của tôi, tôi rất khuyên bạn nên xem nó. Tôi sẽ để một liên kết trong phần mô tả và có thể một chú thích ở đâu đó. Nếu bạn hiểu rằng mã bạn viết cũng là dữ liệu mà bạn có thể sử dụng để làm việc gì đó, thì bạn ổn. Và cũng chỉ vì mã của bạn là dữ liệu có nghĩa là dữ liệu và mã. Vậy khi bạn nhận được dữ liệu, bạn có thể tạo mã và đây là mục đích của Expression Trees.
Trước khi chúng ta bắt đầu, tôi chỉ muốn giải thích nhanh về cấu trúc dữ liệu cây. Có một vài loại cây khác nhau, nhưng chủ yếu chúng ta sẽ xem xét cây nhị phân, loại phổ biến hơn. Ví dụ, nếu chúng ta có biểu thức '1 + 2', điều gì sẽ xảy ra là trước khi chúng ta có thể cộng bất kỳ thứ gì, chúng ta cần biết chúng ta đang cộng gì. Vậy nên chúng ta cộng 1 và 2, vì vậy chúng ta cần biết về 1 và 2 trước khi có thể cộng. Vậy điều chúng ta làm là nói rằng chúng ta muốn cộng, nhưng thực sự chúng ta muốn cộng gì? Chúng ta muốn cộng 1 và sau đó chúng ta muốn cộng 2. Các hình tròn được gọi là nút và các nút cũng có thể được biểu diễn dưới dạng lớp, đối tượng, v.v... Nó là một loại đối tượng chứa một giá trị nào đó của một loại nào đó. Ví dụ ở đây, chúng ta có loại integer và các giá trị mà bạn có thể thấy chúng, và ở đỉnh này, chúng ta thực sự có một operand, đúng không? Vậy nên loại là một phép toán và giá trị chính xác là dấu cộng. Cách bạn duyệt cây là bạn nói rằng bạn muốn làm gì, bạn muốn làm dấu cộng, bạn có gì ở bên trái không? Có, chúng ta có 1. Bạn có gì ở bên phải không? Có, chúng ta có 2. Và sau đó chúng ta cộng chúng lại với nhau. Vậy nên bạn luôn duyệt từ trái sang phải và đó về cơ bản là cấu trúc của cây dữ liệu.
Nếu cây phát triển về kích thước, ví dụ nếu chúng ta nhân với 3, phép nhân được thực hiện trước. Vì vậy, chúng ta phải làm điều này ngược lại, vẫn thực hiện dấu cộng trước hoặc đúng hơn là viết nó trước vì nó phải đến sau hoặc sau phép nhân. Vậy nên chúng ta làm dấu cộng và sau đó chúng ta nói rằng nếu chúng ta muốn cộng, chúng ta cần biết gì về dấu ngoặc ở đây để dễ dàng hơn, chúng ta sẽ cần 1. Vậy nên chúng ta có 1 ở bên trái và 2 x 3 ở bên phải. Trước khi chúng ta có thể nhân bất kỳ thứ gì, chúng ta cần biết về 2 và 3. Vậy nút tiếp theo sẽ là nhân và sau đó ở bên trái của phép nhân chúng ta có 2 và ở bên phải là 3. Vậy đây là tất cả, đây là những gì các biểu thức là. Chúng ta cũng có thể xem xét kỹ hơn và ví dụ liên kết. Ví dụ, nếu tôi nói `var a = 1 + 2`, chúng ta sẽ làm chính xác những gì tôi vừa làm. Vậy nên không có gì khác, nhưng chủ yếu tôi muốn vào cửa sổ cây và những gì tôi sẽ làm là tôi sẽ đánh dấu 1 + 2. Ở đây, bạn có thể thấy chúng ta có biểu thức cộng làm nút trên cùng, đi sang bên trái chúng ta có một biểu thức số nguyên, và bên phải chúng ta lại có một biểu thức số nguyên nữa. Ở dưới cùng, bạn có thể thấy giá trị mà đối tượng chứa và nếu bạn có thể thấy một mẫu chung, chúng ta có biểu thức được nối vào tên của numeric literal hoặc add. Vậy nên, đây về cơ bản là loại gốc của đối tượng gọi là Expression, một lớp trừu tượng từ đó tất cả các lớp khác kế thừa.
Chúng ta sẽ tiếp tục xây dựng trên ví dụ này một chút nhưng chúng ta sẽ nhân nó với 3, giống như chúng ta đã vẽ. Toàn bộ hàm main hơi lớn, nhưng chủ yếu chúng ta sẽ phân tích là `1 + 2 * 3`. Ở đây, bạn có thể thấy chúng ta có biểu thức cộng bên trái, chúng ta có 1 bên phải, chúng ta có biểu thức nhân. Biểu thức nhân giữ giá trị của x ở đây là numeric literal ở bên trái và numeric literal expression 3 ở bên phải. Vậy nên đây là hình dạng của một biểu thức khi bạn viết một số dữ liệu và sau đó nó tạo ra mã dựa trên dữ liệu đó. Thật tình cờ dữ liệu này là cú pháp C#. Vậy nên hãy đưa vào một ví dụ nhỏ để giúp chúng ta bước vào thế giới của Expression Trees trong C#.
Chúng ta có thể có một hàm mà sẽ trả về một integer và chúng ta nói rằng nó sẽ trả về 5. Vậy nên chúng ta chỉ cần gọi hàm và chúng ta sẽ nhận được 5. Chúng ta thực thi và dump, chúng ta chỉ cần xuất nó ra console, bên phải đây chúng ta sẽ thấy số 5. Vậy nên tất cả những gì hàm này làm là xuất ra số 5. Bây giờ tôi muốn sao chép dòng này và bọc hàm trong một biểu thức, và tôi sẽ thêm `_expression` vào cuối. Nếu bạn từng thấy điều này, bạn có thể đã thấy nó và nếu bạn sử dụng Entity Framework hoặc ORM nào đó, nhưng về cơ bản điều này nói rằng chúng ta đang mong đợi một hàm trả về một integer, hãy lưu thông tin về nó. Vậy nên `five_expression` không phải là một hàm, nó là thông tin về một hàm. Sự khác biệt chính giữa hai cái này là đối với `five_function`, chúng ta có thể có một thân phương thức bên trong hàm, nhưng với biểu thức, chúng ta không thể thực sự có một thân phương thức mà chúng ta có thể trả về. Vậy nên điều này sẽ cho bạn những đường gạch đỏ vì thân phương thức bao gồm các câu lệnh không thực sự đánh giá thành các Expression Trees một cách đáng tin cậy. Vậy hãy quay lại và chỉ cần xuất `five_expression` và xem chúng ta nhận được gì. Vì lý do chứng minh, chúng ta sẽ gọi hàm và nó sẽ cho đường gạch đỏ, chúng ta không thể thực thi phương thức. Vậy điều chúng ta có thể làm là chỉ dump biểu thức và những gì chúng ta sẽ nhận được ở bên phải là tất cả thông tin về biểu thức. Bạn có thể thấy loại nút, đúng như tôi đã vẽ các hình tròn, mỗi hình tròn là một nút. Trong trường hợp của chúng ta, nút gốc là lambda vì đây là đối tượng đầu tiên, và sau đó ở thân là một nút gọi là constant. Vậy nên loại nút là constant và lambda và bên phải chúng ta sẽ có parameters. Hiện tại không có parameters nào, vì vậy chúng ta có thể có nhiều parameters và sau đó với thân, thân ở đây chứa nút này, chỉ là một biểu thức đơn lẻ. Vậy nên, điều này sẽ cho bạn biết rằng hàm mà bạn gán cho một biểu thức thực sự là thông tin về hàm đó hoặc biểu thức đó hoặc Expression Tree đó. Bạn có thể duyệt Expression Tree này và bạn có thể làm những thứ như suy diễn từ thông tin của hàm đã được viết. Đây là dữ liệu mà bạn truyền vào biểu thức mà bạn có thể duyệt qua. Đây là cách Entity Framework sẽ đọc các biểu thức mà bạn viết và dựa vào đó, nó sẽ kiểm tra những gì bạn đã viết và dựa trên đó, nó sẽ tạo ra một truy vấn SQL. Nhưng sau đó, điều bạn cũng có thể làm là sử dụng Expression API để tạo các hàm. Vì vậy, khi bạn có một biểu thức và bạn thực sự muốn gọi hàm, bạn có thể lấy biểu thức đó vì hiện tại nó chỉ là dữ liệu về một hàm, bạn có thể biên dịch nó. Sau khi biên dịch, bạn có thể chạy nó và thấy rằng nó thực sự là một hàm. Một khi chúng ta biên dịch nó, đó chính là hàm mà chúng ta đã định nghĩa và chúng ta có thể gọi nó và chúng ta sẽ nhận được 5 ở cuối. Nếu bạn không thoải mái với hai dấu ngoặc ở mỗi bên, chúng ta có thể gọi invoke thay thế và nó vẫn sẽ là 5.
Vậy nên, tôi không muốn quá tập trung vào việc nói về các biểu thức một cách lạc lối. Tôi đã chuẩn bị hai ví dụ. Một ví dụ là đọc các biểu thức, nơi chúng ta sẽ cung cấp một số biểu thức và sau đó cố gắng suy diễn chúng, và sau đó chúng ta sẽ đọc một số dữ liệu và tạo các biểu thức hoặc tạo các hàm tại thời gian chạy. Vậy hãy tiến tới đọc các biểu thức và những gì chúng ta có là một đối tượng user và sau đó là một URL. Vậy chúng ta đang nói rằng bạn sẽ gọi một API sẽ trả về một đối tượng user và điều tôi muốn bạn làm là chọn một số trường trên đối tượng đó. Ví dụ, khi chúng ta tạo URL, chúng ta muốn chọn tên và tuổi, và đây là cách framework thực hiện câu lệnh Select ở đây. Chúng ta đang bao gồm các trường trong URL, vì vậy nếu chúng ta thêm tên, nó sẽ thêm một tham số truy vấn "name" vào URL và phản hồi sẽ là một đối tượng user với tuổi và tên hiện diện. Nếu chúng ta bao gồm cả hai, cả hai sẽ hiện diện. Đây là thiết lập cơ bản.
Như bạn có thể thấy ở đây, chúng ta có thể nhập các chuỗi và đây là một giải pháp dễ dàng, nhưng nếu bạn sử dụng chuỗi, điều có thể xảy ra là bạn có thể thêm một thuộc tính không nên có và các lỗi chính tả có thể xảy ra mọi lúc. Và không có intellisense cho các chuỗi của bạn trừ khi có kiểm tra ngữ pháp nếu bạn có một số plugin trong IDE, nhưng nhìn chung đây là một cách tiếp cận dễ mắc lỗi và trừ khi bạn rất quen với API, bạn sẽ mắc phải những lỗi này. Vậy trong tình huống cụ thể này, bạn có thể sử dụng đối tượng. Bạn có một đối tượng và bạn đã biết các thuộc tính nào sẽ được trả về, vì vậy thông tin về các thuộc tính có thể được trả về đã có trên đối tượng user này. Tất cả những gì chúng ta cần làm là tìm cách nói rằng lấy đối tượng user này và sau đó chọn một thuộc tính. Và chính xác là những gì chúng ta sẽ làm, thay vì chỉ định các chuỗi mà chúng ta muốn chọn, chúng ta sẽ chỉ định các thuộc tính mà chúng ta muốn chọn. Trong trường hợp này, chúng ta có tên và tuổi. Vậy điều đầu tiên tôi sẽ làm không làm phức tạp ví dụ này quá nhiều, chúng ta sẽ tiếp tục vào ví dụ. Đây là một đối tượng user và tôi sẽ chỉ cho bạn cách tiếp cận của mình để trích xuất dữ liệu vì tất cả những gì chúng ta muốn biết là thuộc tính nào mà chúng ta muốn yêu cầu, làm thế nào để chọn một thuộc tính thường thì đúng không? Thường thì bạn sẽ có một đối tượng user mới và sau đó bạn sẽ nói `user.Name`. Đây là cách bạn chọn tên, sau đó bạn có thể dump nó và nếu bạn đã đặt tên thành một thứ gì đó, bạn sẽ nhận được đầu ra đó.
Điều chính mà chúng ta muốn mô phỏng là lấy một đối tượng user và chọn tên, và chúng ta có thể và làm thế nào để đưa hành vi này vào một biểu thức. Vậy đây là điều bạn cần nghĩ đến, bạn cần đưa hành vi này vào biểu thức này. Vậy nên bạn không nghĩ về điều này như là hàm bạn sẽ thực thi, hãy nghĩ về nó như là cú pháp C# mà bạn sẽ đọc như dữ liệu. Thay vì viết chuỗi này, bạn sẽ viết cú pháp C# sẽ được kiểm tra tại thời gian biên dịch và nó thực sự không chạy mà chỉ sử dụng như dữ liệu. Vậy nên, chúng ta có thể tạo một biểu thức và đối với biểu thức này, chúng ta cần là user và chúng ta cần tên. Tôi sẽ lặp lại điều này, chúng ta không thực sự cần giá trị của user để truyền vào hàm này, chúng ta không đang viết một hàm, chúng ta đang viết một đoạn mã C# chỉ để được đọc. Vậy nên, chúng ta sẽ tạo một hàm chấp nhận một tham số là đối tượng user và nó sẽ trả về một đối tượng, vì vậy đây sẽ là một kiểu trả về nào đó, chúng ta không quan tâm là nó là một chuỗi hay một integer, chúng ta chỉ muốn trả về một thứ gì đó. Sau khi chúng ta có biểu thức này, gọi nó là `expression` và tôi sẽ nói rằng chúng ta sẽ lấy user và sau đó chúng ta sẽ chọn `username`. Vậy nên, nếu chúng ta muốn chọn `H` và nếu chúng ta mắc lỗi chính tả, chúng ta sẽ nhận được kiểm tra tại thời gian biên dịch, chúng ta không thể chọn các thuộc tính mà chúng ta chưa đăng ký trên đối tượng user của mình. Vậy nên, chúng ta không thể mắc lỗi ngoài việc mắc lỗi chính tả trên đối tượng user đó.
Vậy hãy điền biểu thức và xem chúng ta có gì ở đây. Tôi sẽ làm nhỏ hơn một chút, loại không phải lambda, chủ yếu tôi đang tìm tên hoặc tuổi và tất cả những gì tôi muốn làm là xuất ra dưới dạng chuỗi tuổi và tên. Vậy nên khi chúng ta đang chọn, chúng ta đang chọn tuổi, vậy tuổi sẽ có một biểu thức loại convert. Vậy biểu thức là loại cơ bản và sau đó có rất nhiều lớp như unary expression hoặc property expression, thực sự là member expression, hơi lẫn lộn về các loại thực tế mà bạn có thể nhận được đôi khi bạn có thể lấy tên từ loại nút đôi khi bạn có thể lấy tên từ unary expression và đây chỉ là một quá trình thử và sai ở thời điểm này. Vậy nên, điều chúng ta thực sự muốn là tên của tham số, vậy nên chúng ta muốn lấy member này và lấy tên của nó vì đây là cách chúng ta sẽ suy diễn chuỗi nào sẽ đi lên URL. Vậy hãy zoom vào thân và điều tôi muốn chỉ cho bạn là tại điểm này loại nút là convert, vậy đó là một unary expression và nếu chúng ta chọn tên, nó sẽ là một loại nút khác, vậy nên loại nút là member access. Lý do tại sao nó làm vậy là vì nó cần chuyển đổi từ integer sang object. Nhưng đối với string thì dễ dàng hơn một chút, string là kiểu object. Nhưng về cơ bản bạn có thể thấy sự khác biệt là thân có thể là bất kỳ biểu thức nào. Vậy nên, điều sẽ xảy ra là thân này thực sự là một biểu thức được trả về từ biểu thức lambda. Vậy nên, biểu thức lambda là một lớp cụ thể và Expression là một lớp trừu tượng từ đó biểu thức lambda kế thừa. Chúng ta sẽ đến việc sử dụng các lớp thực tế và tạo chúng trong ví dụ tạo, nhưng vâng, tất cả những gì tôi ám chỉ là nếu chúng ta vào thân và sau đó cố gắng lấy member, chúng ta sẽ không thể vì nó không có ở đó. Member tồn tại trên property expression hoặc member access expression và nếu chúng ta cố gắng làm tương tự với age, ví dụ bây giờ chúng ta có loại nút là convert hoặc unary expression, nếu chúng ta cố gắng chọn operand nơi member của chúng ta tồn tại, chúng ta không thể vì operand là gì? Là một biểu thức, chúng ta cần thực hiện một chuyển đổi kiểu. Vì ví dụ này hơi khó hơn, hãy cùng đi qua trước hoặc thực ra hãy làm cho ví dụ trước. Vậy hãy lấy thân, đặt nó vào một tham số như vậy và những gì chúng ta sẽ làm là chúng ta sẽ bắt đầu với tên trước. Chạy nó và để tôi dump thân một lần nữa, chúng ta có thể xem nó khi chúng ta làm việc. Bạn có thể thấy loại nút là member access, vậy nên chúng ta có thể làm gì? Vâng, và thường thì cách tôi cảm nhận nó là chuyển thân thành property expression, nếu thân là property expression thì điều này có thể hoạt động, nếu không thì tôi sẽ không nhận được gì. Vậy nên, tôi sẽ cố gắng thay đổi nó thành member expression hoặc member access expression để tôi có thể sử dụng nó và cơ bản là sau đó trên member expression tôi có thể zoom vào member và lấy tên của nó và sau đó dump nó và có thể chuyển đổi nó về chữ thường để phù hợp với URL và đặt dấu chấm phẩy ở cuối và cuối cùng tôi sẽ có tên. Nếu tôi chọn H thì tôi sẽ không nhận được age ở dưới vì thân là một unary expression. Vậy nên, nếu bạn cố gắng điều hướng theo loại nút, nếu bạn nói rằng thân là một invert expression, tôi không biết convert expression là gì, có vẻ như tôi không chắc tại sao API này lại lẫn lộn như vậy, nhưng những thứ bạn có thể làm với nó cuối cùng rất ấn tượng. Vậy trên biểu thức của chúng ta, unary expression và sau đó chúng ta có thể zoom vào operand và một lần nữa operand nếu chúng ta di chuột qua nó sẽ là một biểu thức, vậy nên tại điểm này chúng ta sẽ cần thực hiện một chuyển đổi khác, nhưng tôi sẽ không làm quá nhiều ví dụ, nhưng cơ bản ở đây tôi chỉ sẽ chuyển đổi nó vì tôi biết nó sẽ là member access, giống như ở đây. Vậy nên, chúng ta sẽ gọi là member access và sau đó có thể lấy member và lấy tên của nó và chuyển đổi nó về chữ thường. Vậy nên, chúng ta sẽ làm giống như trên và dump nó. Vậy nên, khi chúng ta chạy điều này ở dưới cùng, chúng ta sẽ có age. Vậy nên, điểm ở đây lại là chúng ta không thực thi hàm mà chúng ta viết ở đây, đây là mã dưới dạng dữ liệu. Chúng ta đã viết mã chỉ để sử dụng nó như một cấu trúc dữ liệu và nó đến dưới dạng Expression Trees mà sau đó chúng ta có thể sử dụng cái gì đó như thế này để kiểm tra nó. Vậy nên hãy kết nối nó lại vì ở đây chúng ta có thể làm một stripper là một mảng chuỗi để chọn các trường. Hãy thay thế tất cả các dấu hỏi này bằng các selector yield và những gì chúng ta sẽ cung cấp là một mảng các biểu thức trên các đối tượng user, giống như vậy. Vậy nên, bây giờ tất cả những gì chúng ta phải làm là lấy phần còn lại của hàm này, ví dụ hãy đặt nó ở đây, biểu thức, chúng ta sẽ phải đặt nó trong một vòng lặp để chúng ta sẽ phải lướt qua tất cả các selector và lấy từng cái một. Vậy nên, tôi sẽ tích lũy tất cả các kết quả ở đây, vậy nên tôi cần một danh sách kết quả. Vậy nên, tôi sẽ gọi nó là results, rồi tôi sẽ đặt một vòng lặp foreach để duyệt qua từng selector. Ở điểm này, chúng ta có thể lấy selector, lấy thân thay vì dump bất cứ thứ gì, chúng ta sẽ thêm chúng vào fields và chuyển đổi chúng về chữ thường và làm tương tự ở đây. Tôi sẽ sao chép lại lần nữa, đây không phải là triển khai chính xác nhất, chỉ là một ví dụ đơn giản để cho bạn biết những gì có thể làm. Vậy nên chúng ta lấy tất cả các trường ở điểm này, chúng ta sẽ có một danh sách các trường và sau đó chúng ta có thể làm tương tự ở đây và cung cấp các trường một lần nữa. Vậy nên, thay vì cung cấp các chuỗi, chúng ta sẽ nói rằng chúng ta có một user và trên user, chúng ta sẽ chọn tên và sau đó chúng ta có thể chọn user và chọn tuổi. Vậy nên, ở điểm này chúng ta chỉ đang chọn các thuộc tính và điều này được kiểm tra tại thời gian biên dịch, vì vậy chúng ta không thể mắc lỗi chính tả trừ khi chúng ta mắc lỗi trên chính đối tượng, điều này dễ dàng hơn một chút để nhận ra trong các buổi rà soát mã. Vậy hãy xem kết quả có giống nhau không. Vậy nên, chúng ta sẽ nói số một ở đây và số hai ở đây, hãy chạy nó. Có vẻ như tôi chưa cung cấp các trường đã chọn cho phần này của mã, vậy nên hãy chạy lại và ở đây bạn có thể thấy các URL mà chúng ta tạo ra cho API giống hệt nhau trừ một cái không dễ mắc lỗi. Tuy nhiên, có một chút phức tạp đằng sau nó, nhưng nếu bạn có một cơ sở mã lớn với rất nhiều API và các cuộc gọi điểm cuối, điều này thực sự có thể giúp bạn tiết kiệm rất nhiều rắc rối.
Vậy nên, để tóm tắt phần này, chúng ta đang viết mã không để thực thi nó mà là để đọc nó như thông tin, và đây là ví dụ về cách mà Entity Framework sử dụng, hoặc thay vào đó là đọc các biểu thức thay vì tạo ra chúng."
"Vậy hãy chuyển sang ví dụ tiếp theo. Ở đây chúng ta sẽ tạo các biểu thức, điều này có thể rất hữu ích và hy vọng ví dụ này sẽ cho bạn thấy lý do tại sao. Giả sử bạn đang xây dựng một API và bạn muốn cho phép người dùng lọc kết quả của bạn, hoặc nói cách khác là sắp xếp kết quả. Vậy bạn muốn sắp xếp kết quả của mình hoặc lọc kết quả của bạn thường thì bạn sẽ làm như thế nào? Đúng rồi, bạn sẽ thêm một dấu chấm vào liên kết của mình và thêm một thứ gì đó giống như biểu thức. Nhưng chúng ta sẽ lọc mọi thứ mà tuổi lớn hơn năm. Thông thường, mọi thứ được thực hiện bằng LINQ trong C# ngày nay. Thế nếu bạn có thể sử dụng một chuỗi để xây dựng một loại biểu thức mà bạn có thể truyền vào mệnh đề where hoặc một selector? Các khả năng là vô hạn. Vậy chúng ta hãy tưởng tượng rằng chúng ta sẽ truyền một giá trị trong truy vấn, header hoặc body mà sau đó sẽ được sử dụng để chọn một trường hoặc lọc nó. Trong trường hợp này, nó sẽ là chọn trường. Chúng ta có một lớp nào đó với một trường là "word" và một số, và ở đây tôi khởi tạo lớp đó. Nếu tôi muốn chọn một trường, thường thì tôi phải đặt một câu lệnh if, đúng không? Nếu tôi đang lấy "word", tôi sẽ trả về "word". Nếu tôi đang lấy "number", tôi sẽ trả về "number". Vậy điều chúng ta muốn làm là tiếp cận điều này một cách linh hoạt hơn và giả sử rằng nếu tôi gửi một số, tôi sẽ nhận được một số, nếu tôi gửi một từ, tôi sẽ nhận được một từ. Vậy nên tôi muốn tái tạo cùng một hành vi. Chúng ta sẽ tiếp tục với "word" ở đây và hãy nói về cách bạn thực sự tạo ra các biểu thức lambda hoặc chính các biểu thức. Lambda là một loại biểu thức và còn có rất nhiều loại biểu thức khác. Tương tự như cách bạn có lớp Type trong reflection để đi vào thế giới reflection và bắt đầu đọc thông tin về một loại cụ thể, trong thế giới biểu thức, bạn có lớp Expression. Với lớp Expression, bạn có một lớp tĩnh với các hàm tĩnh, hầu hết các hàm này sẽ trả về một loại biểu thức nào đó, như Convert, Checked, Continue và Add Expression. Vậy nếu bạn muốn thực hiện một phép cộng, đây là cách bạn tạo ra chúng.
Quay lại tổng quan đơn giản này, tôi muốn đi qua biểu thức này và vẽ lại nó dưới dạng nút để chúng ta biết mình đang tạo gì, và điều đó sẽ giúp bạn hiểu rõ cấu trúc cây nếu bạn hiểu cách hình thành nó. Vậy hãy bình luận phần này đi, chúng ta sẽ chỉ tập trung vào biểu thức và chỉ dump nó. Nếu chúng ta muốn tạo biểu thức này, đây là cách chúng ta sẽ làm và để đơn giản hơn một chút, tôi sẽ lấy tên và rút gọn nó một chút. Thường thì gốc là thứ cuối cùng cần được gọi, đúng không? Vậy nên khi quay lại, ở gốc chúng ta có lambda. Đây là biểu thức lambda, chúng ta có các tham số. Đây không phải là một cây nhị phân, chỉ là các nút trên đối tượng lambda. Các tham số có thể có rất nhiều thứ, chúng có danh sách các loại. Nếu chúng ta thêm một integer và thêm một tham số khác vào hàm, danh sách này sẽ tăng lên với mỗi tham số. Ở đây chúng ta có "user". Bất kỳ tham số nào khác cũng sẽ thêm một nhánh. Tiếp theo là thân, và thân chỉ là một biểu thức khác. Biểu thức chính mà nó giữ là biểu thức thuộc tính, và biểu thức thuộc tính là một loại nút của member access. Vậy nên, khi chúng ta có một nút thực hiện phép cộng hoặc nhân, đây là loại phép toán, tôi sẽ nói rằng nó là dấu chấm vì đó là cách chúng ta ký hiệu trong cú pháp, chúng ta đặt dấu chấm để truy cập thứ gì đó. Đây là "s1", tôi sẽ gọi là dấu chấm bên phải. Ở bên trái là gì? Ở bên phải là gì? Hoặc chính xác hơn, chúng ta đang chọn gì? Chúng ta đang chọn một tên. Vậy nên, chúng ta sẽ đặt tên ở bên phải. Không chắc chắn liệu nó có phải bên phải không vì nó không giống như một cây nhị phân, nhưng dù sao tôi cũng sẽ đặt tên ở đây và bạn hiểu rằng đây là "user".
Khi cây được duyệt, nó luôn từ trái sang phải. Vì vậy, bạn có thể thấy rằng trước đây, giống như injection phụ thuộc, bạn cần biết về tất cả các phụ thuộc trước khi bạn có thể gom chúng lại hoặc trước khi bạn có thể lấp đầy container. Vậy trước khi chúng ta có thể xây dựng lambda, chúng ta cần biết về các tham số của mình trước khi chúng ta có thể xây dựng thân lambda, chúng ta cần biết thân lambda bao gồm những gì. Vậy nên chúng ta sẽ bắt đầu từ các nút dưới cùng và đặt chúng lên các nút cao hơn cho đến khi chúng ta có được một lambda.
Quay lại biểu thức grading và những gì chúng ta đang tìm kiếm ở đây là selector. Vậy chúng ta muốn làm gì? Chúng ta muốn tạo ra một lambda thực hiện hoạt động chọn một trường hoặc thuộc tính cụ thể và cho phép chúng ta lấy giá trị, vì đó là những gì selector sẽ làm. Nếu chúng ta đặt một điều kiện, nó sẽ chạy selector và trả về một giá trị. Nó giống như chỉ có một hàm với một selector động dựa trên tham số khác. Tuy nhiên, làm thế nào chúng ta làm điều này? Trước khi chúng ta có thể truy cập tên trên user, chúng ta cần biết về user, vì vậy chúng ta cần một tham số. Chúng ta cần bên trái trước khi làm bên phải, vậy tham số trước.
Chúng ta sẽ tạo một tham số, nó cần một loại. Loại chúng ta làm việc với là một lớp nào đó. Vậy nên, chúng ta gọi nó là "parameter" để đơn giản hơn. Nếu bạn tưởng tượng hộp mà bạn có thể đặt các phần tròn và vuông, nơi chúng ta có một hàm như một thứ không có lỗ hổng, và ngay khi chúng ta tạo một tham số, chúng ta mở một lỗ hổng đó giống như kích thước của lớp nào đó. Vì vậy, chúng ta chỉ đặt các kích thước lớp vào đó. Vậy nên, những gì chúng ta đã làm ở đây là tạo phần này. Không phải là loại tham số cụ thể, mà là loại user. Bạn sẽ thấy cách chúng ta sẽ đặt khi tạo lambda, bạn sẽ thấy cách chúng ta sẽ điền các tham số của lambda.
Khi chúng ta có user, chúng ta có thể truy cập tên của user. Chúng ta muốn truy cập vào và nếu nhớ đúng, nó được gọi là member access. Vậy hãy tạo một biểu thức member access, để tạo một biểu thức member access và không phải white, chúng ta muốn là property hoặc field. Vậy nên, tạo một member expression đại diện cho việc truy cập vào một property hoặc field. Bạn có thể thấy cách các tên không khớp ở đây, đây là lý do tại sao việc hiểu bạn đang làm gì là quan trọng, khả năng diễn đạt nó. Bởi vì nếu bạn có thể nói bạn muốn làm gì nhưng không biết cách diễn đạt nó trong C#, thì sẽ rất dễ để tìm kiếm vấn đề này trên Google. Bạn có thể diễn đạt câu hỏi của mình trong thanh tìm kiếm và nhận được câu trả lời từ Stack Overflow hoặc những nơi khác. Vì vậy, việc đặt tên là khó khăn, nhưng đó là lỗi có thể tha thứ cho những người tạo ra C#. Vì vậy, field của bạn vẫn dễ hiểu nếu bạn sử dụng nó nhiều, bạn sẽ nhớ nó. Vậy nên, field của bạn là gì? Chúng ta có error và hãy nhấn F12 nếu nó cho phép. Ở đây chúng ta có Expression và String. Expression là cái mà chúng ta đang chọn và String là property hoặc field mà chúng ta đang chọn từ tham số. Vậy chúng ta đang chọn gì? Đây là phần thú vị. Chúng ta có thể nói chọn property này và ở thời điểm này, chúng ta có phần này. Vậy bây giờ chúng ta không nhất thiết phải tạo các khối parameter và body, chúng sẽ không chặn, xin lỗi, các nút. Chúng sống trên nút lambda hoặc phần của lớp lambda, giống như khi tôi đã chỉ cho bạn cây, các số hoặc giá trị thực thuộc về biểu thức gì đó. Vì vậy, hãy gọi phần này là accessor. Điều tiếp theo chúng ta muốn là lambda, vậy lambda = Expression.Lambda và phần này có thể khó vì có rất nhiều thứ ở đây. Bạn sẽ cần phải hiểu, có thể cần tìm hiểu qua Google đôi khi. Ở bên phải, nếu bạn đang sử dụng Visual Studio hoặc bất kỳ công cụ nào, bạn có thể truy cập nó bằng cách nhấn F12 bằng cách kiểm tra triển khai hoặc bất cứ gì. Vậy ở đây bạn có thể thấy thân là nơi chúng ta cung cấp body và parameter, đó là những gì sẽ điền vào hai nút đó. Vậy hãy điền chúng đi. Thân là accessor, parameter chúng ta gọi là false ở đây và parameter. Vậy đây là một lambda, đây là một biểu thức lambda. Cuối cùng, chúng ta có thể làm gì với các biểu thức lambda? Chúng ta có thể biên dịch chúng. Sau đó, chúng ta có thể gọi chúng và chúng ta không có invoke cụ thể ở đây, chúng ta có dynamic invoke nơi chúng ta có thể cung cấp các đối tượng của mình và tìm kiếm. Tôi không chắc liệu chúng ta có thể làm điều này không, hãy chọn property thay vì vậy. Điều này không được, vậy nên hãy dùng dynamic invoke thay thế. Vậy chúng ta sẽ thấy gì? Chúng ta có hai hello world. Nếu chúng ta hoán đổi chúng, chúng ta sẽ chọn số. Vậy nên đây là giải pháp cứng nhắc, không thể mở rộng. Vậy chúng ta làm gì? Chúng ta tạo một selector sử dụng Expression API và đây là dữ liệu trước khi chúng ta biên dịch nó. Bạn có thể biên dịch nó, lưu hàm, vì vậy điều này có thể được cache. Có các cách tiếp cận để làm cho nó nhanh hơn, giống như thiết lập một lần. Vì vậy, có rất nhiều thứ thú vị bạn có thể làm với điều này. Như bạn thấy, nhiều framework phổ biến sử dụng nó và hy vọng video này giúp bạn hiểu khái niệm. Nhớ rằng mã bạn viết là dữ liệu và dữ liệu là về, đó là khái niệm chính cho phép bạn nghĩ ra các giải pháp cho vấn đề bằng cách sử dụng reflection và Expression Tree API. Nếu bạn thích video này, hãy nhấn like, đăng ký kênh. Nếu bạn có bất kỳ câu hỏi nào, hãy để lại chúng trong phần bình luận. Tôi livestream vào các ngày thứ Tư và Chủ nhật, vì vậy nếu bạn quan tâm, đừng quên tham gia kênh Discord. Tôi cập nhật rất nhiều ở đó và như mọi khi, hy vọng sẽ gặp bạn trong các video khác của tôi.
Nhận xét
Đăng nhận xét