استفاده نادرست از IEnumerable و پیامدهای آن
نویسنده: وحید نصیری
تاریخ: ۱۴۰۴/۰۲/۲۳ ۰۷:۰۴
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
foreach استفاده میکنید، عملیات LINQ را اجرا مینمایید، یا دادهها را از یک منبع داده پردازش میکنید، تقریباً همیشه به IEnumerable وابسته هستید. با این حال، مشکل اینجاست که بسیاری از توسعهدهندگان به طور کامل از زمان و نحوه صحیح استفاده از آن آگاه نیستند. این ناآگاهی میتواند منجر به اشتباهات جدی مانند تکرار غیرضروری عملیات، کاهش کارایی (Performance drop)، نشت حافظه (Memory leak) و استفاده نادرست از رابطهای برنامهنویسی کاربردی (API) شود. در این مقاله، به بررسی نحوه عملکرد IEnumerable، دلیل استفاده از اجرای تأخیری (Deferred Execution) در آن، پنج اشتباه رایج در استفاده از آن و در نهایت، روشهای صحیح استفاده و بهترین شیوههای کاری (Best Practices) خواهیم پرداخت.IEnumerable چیست؟IEnumerable یک رابط (Interface) است که متدی به نام GetEnumerator() را تعریف میکند. این متد یک شیء از نوع IEnumerator برمیگرداند که از طریق آن میتوان تکتک عناصر یک مجموعه را پیمایش کرد. تعریف ساده این رابط به صورت زیر است:public interface IEnumerable
{
IEnumerator GetEnumerator();
}IEnumerable<T>, علاوه بر اینکه ایمنی نوع (Type-safe) را فراهم میکند، به خوبی با LINQ سازگار است و امکانات قدرتمندی را برای کار با مجموعهها در اختیار توسعهدهندگان قرار میدهد.IEnumerableIEnumerable، استفاده از مفهوم اجرای تأخیری (Deferred Execution) است. این بدان معناست که تا زمانی که فرآیند پیمایش (مانند استفاده از حلقه foreach) آغاز نشود، هیچ کدی اجرا نخواهد شد. به مثال زیر توجه کنید:IEnumerable<int> GetNumbers()
{
Console.WriteLine("Generating numbers...");
yield return 1;
yield return 2;
yield return 3;
}
var nums = GetNumbers(); // در این مرحله هیچ اتفاقی نمیافتد.
foreach(var n in nums) // اجرای واقعی در این قسمت صورت میگیرد!
{
Console.WriteLine(n);
}IEnumerable است. در نظر بگیرید کدی مشابه زیر:var data = GetData(); // متدی که IEnumerable<string> برمیگرداند var count = data.Count(); // شمارش اول var first = data.First(); // شمارش دوم var list = data.ToList(); // شمارش سوم
IEnumerable تکرار میکنید (چه با استفاده از متدهایی مانند Count(), First() یا تبدیل آن به یک لیست با ToList()), یک شمارش جدید از منبع داده یا منطق تولید آن انجام میشود. اگر GetData() شامل عملیات سنگینی مانند خواندن از پایگاه داده باشد، این تکرار غیرضروری میتواند منجر به کاهش شدید کارایی شود.var data = GetData().ToList(); // تنها یک بار شمارش انجام میشود.
ToList() یا foreach روی آن اعمال نشود، هیچ دادهای واکشی نمیشود.var query = db.Users.Where(u => u.IsActive).Select(u => u.Name); // در اینجا دادهای وجود ندارد، فقط یک عبارت تعریف شده است.
query تغییر کند، ممکن است در زمان اجرای نهایی، نتایج متفاوتی به دست آید که با انتظار اولیه شما مغایرت داشته باشد.var result = query.ToList();
IEnumerable<string> را به عنوان ورودی میگیرد و عناصر آن را ثبت میکند:LogItems(GetData());
void LogItems(IEnumerable<string> items)
{
foreach (var item in items)
{
Console.WriteLine(item);
}
}GetData() شامل 100 فراخوانی به پایگاه داده باشد، هر بار که LogItems اجرا شود، این 100 فراخوانی مجدداً انجام خواهند شد.IEnumerable را با استفاده از .ToList() ارزیابی کنید تا فقط یک بار اجرا شود.Dispose() برای IEnumeratorIEnumerator استفاده میکنید، فراموش کردن فراخوانی متد Dispose() میتواند منجر به نشت حافظه شود.IEnumerator<int> enumerator = GetNumbers().GetEnumerator();
while(enumerator.MoveNext())
{
Console.WriteLine(enumerator.Current);
}
// فراموش شده: enumerator.Dispose() انجام نشد!foreach یا بلاک using استفاده کنید:foreach (var item in GetNumbers()) { ... } // روش امنyield return برای تولید دادههای بزرگ به صورت تدریجی، کارآمد است. در حالی که این روش از نظر مصرف حافظه میتواند بهینه باشد، اما میتواند منجر به مشکلات دیگری شود:public IEnumerable<int> GetLargeData()
{
for(int i = 0; i < 1000000; i++)
{
yield return i;
}
}IAsyncEnumerable در سیشارپ 8) را در نظر بگیرید:await foreach(var item in GetDataAsync()) { ... }IEnumerable.ToList() یا .ToArray() برای مادیسازی آنها استفاده کنید.yield return، مصرف حافظه و زمان را در نظر بگیرید.IEnumerable در دنیای سیشارپ مانند یک تیغه دولبه است؛ اگر نحوه استفاده صحیح از آن را بدانید، یک جنگجوی کد خواهید بود، و اگر ندانید، ممکن است به خود آسیب بزنید. IEnumerable را دست کم نگیرید. اجرای آن تأخیری، تنبل و در عین حال بسیار قدرتمند است. استفاده صحیح از آن میتواند منجر به افزایش کارایی شود، در حالی که استفاده نادرست میتواند باعث بروز اشکالات پنهان و کاهش کارایی گردد.