عرشیا دنیابین
عرشیا دنیابین
خواندن ۴ دقیقه·۲ سال پیش

چگونه IEnumerable میتواند Performance رو نابود کند؟


چندی پیش به نکته ایی در فضای نت برخوردم که گفتم بهتره خودم تستش کنم و توی ویرگولم منتشرش کنم.
اون هم نکته ایی در استفاده از IEnumerable بود :
عرض کنم خدمت حضور انورتون که قطعا هرکسی که مقداری سی شارپ کد زده باشه کالکشن های سی شارپ رو میشناسه IEnumerable هم یکی از اون هاست
فرض میکنیم ما یه دیتاسورس داریم و میخوایم از اون دیتا واکشی کنیم برای این مثال من یه فایل csv کوچیک درست کردم و میخوام ازش دیتا بخونم

Data Source
Data Source
Data Model
Data Model

توی این مثال ما میخوایم که دیتامون رو از فایل بخونیم به مدلمون مپ کنیم و در نهایت Count اش رو بگیریم و نمایشش بدیم
من داخل GetData دیتا رو از فایل خوندم کاما هاش رو جدا کردم و به صورت Deferred Execution با yield return برش گردوندم ، اینجا میتونستیم از یه temp آبجکت استفاده کنیم و دیتا رو یکی یکی میگرفتم به آبجکتمون که حالا میتونست یه لیست باشه اضافه کنم و برش گردونم و خب باتوجه به اینکه نمیدونیم با چه حجم دیتایی رو به رو هستیم ممکنه 100,000 رکورد باشه این روش زمان زیادی میگیره و طبیعتا Performance مون رو هم میتونه خراب کنه.

خب توضیح بسه بریم کد رو اجرا کنیم ببینیم مشکل از کجاست :

همونطور که میبینیم وقتی دیباگر از GetData رد میشه نمیره داخل متود ! ، دلیلشم اینه که متود از نوع IEnumerable هستش و این نوع دیتای سمت سرورمونه و هنوز داخل حافظه ریخته نشده
متود زمانی اصطلاحا هیت میشه که ما بخوایم دیتا رو واکشی کنیم و یا عملیات دیگه ایی رو انجام بدیم
همونطور که بعدش میبینید زمانی که ما میخوایم Count اش رو بگیریم دیباگر میره داخل متودمون و 6 تا آیتم رو از فایل برامون میخونه بعد زمانی که وارد حلقه foreach میشه انتظار داریم که مقدار cars پر شده باشه و راحت پیمایشمون اتفاق بیوفته ولی میبینیم که دوباره دیباگر میره توی متود و مجددا دیتار رو میخونه و توی رم میریزه ، به ازای هر تعداد بار دیگه ایی که ما از cars استفاده کنیم یک بار داخل متود رفته و اون رو اجرا میکنه
این دقیقا خود مشکلمونه چون اگر تعداد رکورد هامون زیاد باشه این اتفاق Performance مون رو میترکونه.
حالا راه حل این مشکل چیه ؟
اینه که دیتا رو همون ابتدا که تابع رو صدا میکنیم داخل لیست بریزیم.

GetData.ToList()


البته خیلی بهتره زمانی اینکار رو انجام بدیم که مطمئن هستیم دیتامون وجود داره و قرار نیست متودمون نال برگردونه و یا حتی مقدار نال بودن رو چک کنیم تا از Runtime - Error جلوگیری کرده باشیم (ArgumentNullException)


یکبار کدمون رو به این شکل اجرا میکنیم :

میبینیم که دیباگر دیگه زمانی که به حلقه foreach میرسیم نمیره سراغ متودمون و متغییر cars مقدار داره

همین نکته ی کوچیک میتونه ما رو در استفاده از IEnumerable وارد چالش بکنه .
بهتره که زمانی که صرفا میخوایم دیتا رو بخونیم و پاس بدیم به یک سرویس دیگه از IEnumerable استفاده کنیم اگر قراره رو مپ انجام بدیم و یا بیش از یکبار صداش کنیم قطعا باید دنبال جایگزین های بهتری بگردیم

شاید الان با خودتون بگید که خب این IEnumerable لعنتی به چه دردی میخوره پس ؟!
باید عرض کنم که IEnumerable به دلیل ماهیت Deferred Execution موارد استفاده ی خاصی برای خودش داره به طور مثال زمانی دیتا سورس ما به صورت Concurrent در حال تغییره ، تعریف متود GetData ما به صورت IEnumerable نه تنها بد نیست بلکه دقیقا درست تعریف شده . این ویژگی Deferred Execution کمک میکنه که شما دقیقا موقع پیمایش متود به آخرین مقدار آپدیت شده دسترسی داشته باشید (منظور این است که از زمانی که شما دیتا رو داخل رم میریزید تا زمان استفاده از اون دیتا منقضی نمیشه) در این حالت نه تنها باید بروز مشکل در Performance ما نمیشه بلکه از نوشتن متود های بیهوده دیگه برای Validate کردن دیتامون جلوگیری میکنه.

یه نکته مهم که خیلی ها شاید بهش توجه نکنن ، شما زمانی که دارید ولیدیشن انجام میدید یا حتی منطقی رو پیاده میکنید هم بهتره IEnumerable تون رو ToList() کنید چون دقیقا همین اتفاق میوفته شما وقتی یک IEnumerable رو Select میکنید با لینک دقیقا همون پیمایشه داره اتفاق میوفته و خب طبیعتا Performanceمون رو هم خراب میکنه
ممنونم از همکار خوب و خفنم پویا شاکری عزیز بابت یادآوری این موضوع بهم.


پس میتونیم بگیم شکل نیاز ما در شرایط مختلف میتونه کارایی یک ویژگی رو باز تعریف بکنه به عبارت دیگه نمیتونیم استفاده از IEnumerable رو مضر برای Performance بدانیم فقط باید به جا و بهینه ازش استفاده بشه

سی شارپienumerableperformancecsharpcollection
شاید از این پست‌ها خوشتان بیاید