Ienumerable trong c# là gì

     

Khi sử dụng C# để lập trình, chúng ta có thể đã bắt gặp từ khóa yield rất nhiều lần nhưng rất ít khi chúng ta biết được nó là gì và có tác dụng như nào? Hôm nay, mình sẽ chia sẻ với các bạn những hiểu biết của mình về yield, rất mong những chia sẻ này sẽ có ích cho các bạn trên con đường thành master C# nhé.

Bạn đang xem: Ienumerable trong c# là gì

Trước khi nói về từ khóa yield, chúng ta cùng nhắc lại về IEnumerable.

1. IEnumerable là gì

IEnumerable là một danh sách có các thuộc tính sau:- Danh sách chỉ đọc, không được phép thêm/bớt phần tử- Chỉ duyệt một chiều từ đầu tới cuối danh sách. Sử dụng foreach để thực hiện duyệt

Các bạn có thể tham khảo tại bài viết "IEnumerable và IEnumerator trong C#" tại đây.

2. Yield là gì

Yield là từ khóa sẽ báo hiệu cho trình biên dịch biết rằng phương thức, toán tử, get mà nó xuất hiện sẽ là một khối lặp. Trình biên dịch sẽ tự động sinh ra một class implement từ IEnumerable, IEnumerator để thể hiện khối lặp đó.

Chúng ta hãy xét ví dụ sau:

class Program { private static IEnumerable Ints() { for (var i = 0; i Sau khi biên dịch sang file exe và dịch ngược lại file exe đó, chúng ta thu được class như sau:

internal class Program{ // Methods private static IEnumerable Ints() { this.5__1 = 0; while (this.5__1 5__1; int num2 = this.5__1; this.5__1 = num2 + 1; } } private static void Main(string<> args) { foreach (int num in Ints()) { Console.WriteLine(num); } Console.ReadKey(); } // Nested Types private sealed class d__1 : IEnumerable, IEnumerable, IEnumerator, IDisposable, IEnumerator { // Fields private int 1__state; private int 2__current; private int l__initialThreadId; private int 5__1; // Methods public d__1(int 1__state) { this.1__state = 1__state; this.l__initialThreadId = Environment.CurrentManagedThreadId; } private bool MoveNext() { switch (this.1__state) { case 0: this.1__state = -1; this.5__1 = 0; while (this.5__1 2__current = this.5__1; this.1__state = 1; return true; Label_003F: this.1__state = -1; int num2 = this.5__1; this.5__1 = num2 + 1; } return false; case 1: goto Label_003F; } return false; } IEnumerator IEnumerable.GetEnumerator() { if ((this.1__state == -2) && (this.l__initialThreadId == Environment.CurrentManagedThreadId)) { this.1__state = 0; return this; } return new Program.d__1(0); } IEnumerator IEnumerable.GetEnumerator() => this.System.Collections.Generic.IEnumerable.GetEnumerator(); void IEnumerator.Reset() { throw new NotSupportedException(); } void IDisposable.Dispose() { } // Properties int IEnumerator.Current => this.2__current; object IEnumerator.Current => this.2__current; }}Giờ các bạn lưu ý:HàmIEnumerable Ints() có xuất hiện từ khóa yield và được gán thêm thuộc tínhIteratorStateMachine(typeof(d__1)), với thuộc tính này trình biên dịch sẽ xác nhận hàm trên là một khối lặp (hoặc có thể gọi là state machine).Classd__1 được trình biên dịch sinh ra, implement từ IEnumerable, IEnumerator để thực hiện cho khối lặp trên.

3. Cách sử dụng

Từ khóa yield được sử dụng trong 2 tình huống như sau:

yield return ;yield break;Lưu ý:- Đối với hàm hoặc get sử dụng từ khóa yield bắt buộc phải trả về kiểu dữ liệu là IEnumerable, IEnumerable, IEnumerator hoặc IEnumerator.- Sử dụng trong vòng lặp có điều kiện (nếu không có điều kiện thì trả luôn danh sách nên không tính trường hợp này).- Lưu trạng thái của lần lặp trước.Ví dụ chúng ta có danh sách gồm 10 phần tử từ 0 đến 9 như sau:

private static List MyList = new List { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };3.1. yield returnNếu là hàm không sử dụng yield thì khi return, chương trình sẽ thoát ra khỏi hàm đó.Nếu là hàm sử dụng yield return thì chương trình sẽ trả về dữ liệu và quay lại vòng lặp để thực hiện vòng lặp tiếp theo.Chúng ta xem ví dụ liệt kê các phần tử có giá trị lớn hơn 3 bằng 2 cách như sau:

class Program { private static List MyList = new List { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; private static IEnumerable NoneYield() { var d = new List(); foreach (var item in MyList) { if (item > 3) d.Add(item); } return d; } private static IEnumerable UseYield() { foreach (var item in MyList) { if (item > 3) yield return item; } } static void Main(string<> args) { Console.WriteLine("None Yield"); foreach (var item in NoneYield()) { Console.WriteLine(item); } Console.WriteLine("Yield"); foreach (var item in UseYield()) { Console.WriteLine(item); } Console.ReadKey(); } }Kết quả sẽ như sau:

*
Nếu không sử dụng yield, để trả về danh sách như trên, chúng ta phải tạo ra một danh sách tạm để lưu những phần tử thỏa mãn điều kiện, sau khi lưu hết những phần tử đó mới thực hiện return.Nếu sử dụng yield thì sau khi yield return, chương trình thực hiện tiếp vòng foreach tiếp theo.

3.2. yield break

Nếu vòng lặp sử dụng break, khi gặp lệnh break, chương trình chỉ thoát khỏi vòng lặp, những dòng phía sau và ngoài vòng lặp vẫn được thực hiện.Nếu vòng lặp sử dụng yield break, chương trình sẽ thoát khỏi hàm mà không thực hiện các lệnh phía dưới.Chúng ta xét bài toán: liệt kê các phần tử khác 3 (khi gặp giá trị 3 thì dừng):

class Program { private static List MyList = new List { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; private static IEnumerable NoneYield() { var d = new List(); foreach (var item in MyList) { if (item != 3) d.Add(item); else break; } Console.WriteLine("Done"); // Sử dụng dòng này để phân biệt return d; } private static IEnumerable UseYield() { foreach (var item in MyList) { if (item != 3) yield return item; else yield break; } Console.WriteLine("Done"); // Sử dụng dòng này để phân biệt } static void Main(string<> args) { Console.WriteLine("None Yield"); foreach (var item in NoneYield()) { Console.WriteLine(item); } Console.WriteLine("=============================================="); Console.WriteLine("Yield"); foreach (var item in UseYield()) { Console.WriteLine(item); } Console.ReadKey(); } }Và kết quả sẽ như sau:

*
Khi sử dụng break thì xuất hiện dòng chữ "Done", còn yield break thì không.

4. Một số lưu ý khi sử dụng

4.1. Khi sử dụng yield, chúng ta cần tuân thủ một số yêu cầu sau:

Không dùng yield trong khối lệnh có từ khóa unsafe và trong hàm ẩn danh.

Xem thêm: Cách Làm Món Bạch Tuộc Nhúng Giấm Của Thế Vy, Bạch Tuộc Nhúng Giấm Giòn Ngon

Không sử dụng từ khóa ref hoặc out đi kèm với tham số của hàm khi bên trong hàm sử dụng yield.yield return chỉ được sử dụng trong try catch có finally.yield break được sử dụng trong try catch không có finally.Chỉ nên sử dụng yield trong những trường hợp điều kiện trả về không quá phức tạp. Bản chất của việc sử dụng yield là sinh ra class để thực hiện khối lặp. Trong khối lặp này có sử dụng đoạn mã để kiểm tra điều kiện. Nếu việc kiểm tra này quá phức tạp sẽ ảnh hưởng trực tiếp tới hiệu năng của ứng dụng.4.2. So sánh tốc độ giữa return và yield return

Chúng ta hãy xem ví dụ sau: trả về danh sách1.000.000 phần tử sử dụng 2 cách thức là không sử dụng yield và có sử dụng yield

class Program { private static IEnumerable NoneYield() { var d = new List(); for (var i = 0; i UseYield() { for (var i = 0; i Chúng ta hãy xem kết quả:

*
Bạn hãy thực hiện chạy nhiều lần để xem nhiều kết quả khác nhau. Trung bình chúng ta sẽ thu được là tốc độ xử lý của hàm có yield gấp ít nhất 10 lần. Một con số thật khủng khiếp.

4.2. yield return lưu lại trạng thái của lần lặp trước

Chúng ta hãy xét ví dụ sau:

class Program { private static IEnumerable UseYield() { var total = 1; // 5 for (var i = 0; i Với đoạn code trên ai cũng nghĩ kết quả khi hiển thị ra màn hình là:

12345Nhưng khi chạy chương trình sẽ cho kết quả là:

*

Ohh, chuyện gì xảy ra vậy? Sao kết quả lại là 1 2 4 7 11??? Chúng ta hãy đặt breakpoint tại dòng 1, 3, 5, 6, 8 và thử debug xem các bước thực hiện:Bước 1: Thực hiện chạy chương trình đến dòng 1, tiếp tục thực hiện sẽ nhảy đến dòng 5 => biến total được gán là 1.Bước 2: Bắt đầu vòng lặp và gán i = 0, tiếp tục thực hiện đến dòng 8 => biến total = total + i => có giá trị 1 và yield return giá trị 1. Tiếp tục thực hiện sẽ in 1 ra màn hình.Bước 3: Tiếp tục thực hiện vòng lặp dòng 1, tiếp tục thực hiện sẽ nhảy đến dòng 6 => với giá trị i = 1. Tiếp tục thực hiện total = total + i = 2 => yield return giá trị 2. Tiếp tục thực hiện sẽ in 2 ra màn hình.Bước 4: Tiếp tục thực hiện vòng lặp dòng 1, tiếp tục thực hiện sẽ nhảy đến dòng 6 => với giá trị i = 2. Tiếp tục thực hiện total = total + i = 4 => yield return giá trị 4. Tiếp tục thực hiện sẽ in 4 ra màn hình.Từ bước này chúng ta thấy rằng việc gán biến total = 1 chỉ diễn ra 1 lần, giá trị này được tái sử dụng cho lần lặp tiếp theo. Điều này giải thích cho việc lưu lại trạng thái khi sử dụng yield.Bước 5: các bước được lặp lại cho đến i = 4.

Tạm kết

Trên đây mình đã giới thiệu với các bạn về từ khóa Yield, cách thức sử dụng và các lưu ý khi thao tác với yield. Hi vọng bài viết có ích với các bạn. Nếu có gì còn thắc mắc, hãy để lại comment nhé.


Chuyên mục: Tin Tức