چهار مفهوم، دو محور متفاوت — خیلیها اینها رو قاطی میکنن. بیایید یه بار برای همیشه مرتبشون کنیم.
محور اول: نحوهی پیمایش (for vs foreach)
این دو فقط syntax نیستن، مکانیزم اجراشون فرق داره.
- for: index-based، مستقیم روی array/List<T> کار میکنه. JIT میتونه bounds check رو حذف کنه، پس روی دیتاستِ in-memory معمولاً سریعتره.
- foreach: از enumerator پیمایش میکنه، روی هر IEnumerable<T> جواب میده. با List<T> چون enumerator struct هست allocation اضافه نداره، ولی sync هست؛ یعنی کل داده باید از قبل آماده باشه.
محور دوم: کجا داره فیلتر اجرا میشه (IQueryable vs IAsyncEnumerable)
اینجا دیگه بحث پیمایش نیست، بحث اینه داده از کجا میاد و کِی پردازش میشه.
- IQueryable: منطق فیلتر (Where, OrderBy...) به Expression Tree تبدیل میشه و تا زمان enumerate نشدن اجرا نمیشه. وقتی provider (مثل EF Core) اجراش کنه، به SQL ترجمه میشه و فقط رکوردهای لازم از دیتابیس میاد.
- IAsyncEnumerable: اینجا فیلتر معمولاً قبلاً روی سرور/دیتابیس اعمال شده؛ داستان این interface streamingه، نه query translation. نتیجه بهصورت async، رکورد به رکورد، بدون اینکه منتظر کل دیتاست بمونی، به دستت میرسه.
نکتهی مهم: این دو تا مکمل هم هستن، نه رقیب.
یه جریان معمول این شکلیه:
IQueryable<User> query = dbContext.Users
.Where(u => u.Age > 30); // هنوز روی DB اجرا نشده
await foreach (var user in query.AsAsyncEnumerable())
{
Process(user); // هر رکورد همون لحظهای که از DB میاد پردازش میشه
}
اول با IQueryable فیلتر رو روی دیتابیس میبری (SQL WHERE)، بعد با IAsyncEnumerable نتیجه رو بهصورت stream میخونی، بدون اینکه کل نتیجه رو یکجا توی List بریزی.
جمعبندی روی یک جدول
| for | foreach | IQueryable | IAsyncEnumerable | |
|---|---|---|---|---|
| نوع | حلقه (پیمایش) | حلقه (پیمایش) | نوع داده (کوئری) | نوع داده (استریم) |
| منبع مناسب | array/List در حافظه | هر IEnumerable | دیتابیس (EF Core) | هر منبع async (DB, API, فایل) |
| زمان اجرای فیلتر | - | - | روی دیتابیس، هنگام enumerate | معمولاً از قبل روی سرور اعمال شده |
| مصرف حافظه | کم (دیتای کوچیک) | کم (دیتای کوچیک) | کم (فقط نتیجهی فیلترشده) | خیلی کم (رکورد به رکورد) |
| بلاککننده؟ | بله (sync) | بله (sync) | تا enumerate نشه اجرا نمیشه | نه، async |
قانون ساده: برای دیتای کوچیک و در حافظه، بین for و foreach بر اساس خوانایی کد انتخاب کن، تفاوت performance ناچیزه. برای دیتای دیتابیسی، همیشه IQueryable رو تا آخرین لحظه نگه دار تا فیلتر به SQL ترجمه بشه؛ و وقتی حجم نتیجه بالاست یا منبع async هست، IAsyncEnumerable رو برای مصرف نتیجه بهکار ببر.
#dotnet #csharp #performance #asynchronous