Sepehr Golpazir
Sepehr Golpazir
خواندن ۶ دقیقه·۳ سال پیش

معرفی و توضیح یک تابع جدید LINQ در دات نت 6

مایکروسافت در 8 نوامبر سال گذشته, نسخه جدید زبان برنامه نویسی سی شارپ تحت عنوان سی شارپ 10 و نسخه جدید فریم ورک خود را نیز تحت عنوان NET 6. معرفی نمود و تغییرات و افزونه های زیادی بر زبان و کتاب خانه خود نیز اعمال کرد.

در ورژن جدید دات نت, شاهد شماری تغییراتی بر کتابخانه معروف Linq نیز بودیم که درباره کارکرد و نحوه استفاده آن ها اینجا میتوانید مطالعه کنید.

در این نوشته امروز میخواهیم یک تابع جدید این کتاب خانه و نحوه کارکرد و سناریو های استفاده آن را مورد بررسی قرار دهیم و آن تابع تابع جدید ()TryGetNonEnumeratedCount است.

اما قبل از ورود به مبحث معرفی و توضیح باید با هم مروری کوتاه بر پیش زمینه روش های استفاده از آرایه ها در زبان برنامه نویسی سی شارپ داشته باشیم.

به طور کلی, ما 2 روش برای دسترسی به اعضای یک مجموعه داریم.

روش اول تحت عنوان Index کردن آرایه شناخته میشود که برای استفاده از آن پیشنیاز هایی باید فراهم گردند

  • نیاز اول: آرایه مقادیر ما باید در حافظه به صورت یک بلوک پیوسته از مقادیر تخصیص پیدا کند یا allocate شود.
  • نیاز دوم: طول آرایه مقادیر تخصیص یافته باید ثابت و مشخص باشد.

به علت طبیعت strongly typed بودن زبان برنامه نویسی سی شارپ, تمامی سازه های داده که به صورت مستقیم از data type ها آرایه استفاده میکنند یا از آن ها ارث بری کرده اند هر دو این نیاز ها را برآورده میکنند.

کلاس هایی مانند:

List<T> , Array, Stack<T> , Queue<T> , ...

همگی از ساختار یک آرایه داخلی استفاده میکنند و به این علت همگی دارای یک پراپرتی Count هستند که برای پیدا کردن مقدار آن از طول آرایه داخلی خود استفاده میکنند.

نکته!

دقت کنید که گفتم "استفاده میکنند" و حتما همیشه مقدار پراپرتی Length آرایه داخلی برابر با مقدار Count نمی باشد!

دسترسی به و پیدا کردن تعداد عناصر یک مجموعه در سازه هایی که از Index کردن استفاده میکنند بسیار آسان و سریع میباشد! چرا که تمامی عناصر در یک بلوک پیوسته حافظه کنار هم قرار دارند. این آرایه نیز به علت ماهیت reference type بودن در قسمت Heap حافظه رم ذخیره میشود و اشاره گر مربوطه به آن در قسمت حافظه Stack در واقع آدرس خانه اول این آرایه است!

وقتی برنامه نویس به آرگومان اندیس این آرایه یک عدد پاس میکند, کامپایلر آن عدد را با آدرس خانه اول جمع میکند تا به شی مورد نظر در حافظه heap برسد. در واقع به این علت است که عنصر اول مجموعه ها همیشه اندیس 0 را دارد چون اشاره گر مجموعه در حالت عادی همیشه به خانه اول اشاره میکند و برای دسترسی به خانه اول کافی است مقدار آن را با 0 جمع کنیم!
دسترسی به عناصر آرایه به آسانی یک عملیات جمع یا تفریق اندیس با آدرس حافظه صورت میگیرد.
دسترسی به عناصر آرایه به آسانی یک عملیات جمع یا تفریق اندیس با آدرس حافظه صورت میگیرد.

و اما روش دوم, تحت عنوان enumeration شناخته میشود.

لغت enumeration در بعد لغوی به معنا شمارش میباشد و عملکرد نرم افزاری آن نیز منعکس کننده این اسم میباشد از آن رو که برای دسترسی به خانه های مختلف یک مجموعه با استفاده از روش enumeration, ما می بایست از خانه اول شروع کرده و خانه به خانه مجموعه را طی کرده و شرط مورد نظر خود را بر روی هر خانه فعلی بسنجیم.

یک مزیت رویکرد enumeration این میباشد که شروط ذکر شده در بخش Index کردن را ندارد!

نکته مهمی که باید در نظر بگیریم این است که هر سازه ای که از ساختار آرایه استفاده میکند نیز می تواند از enumeration استفاده کند! چرا که پی نمودن خانه های یک آرایه پیوسته در حافظه اصلا فرایند پیچیده ای نیست!

این بدین معنی است که مقادیر مجموعه مورد نظر ما میتوانند به صورت پیوسته یا ناپیوسته در حافظه حضور پیدا کنند.

در زبان سی شارپ, مجموعه هایی که برای پیمایش آن ها گذینه enumeration موجود میباشد از رابط IEnumerable ارث بری کرده و آن را implement کرده اند.

رابط IEnumerable تنها یک تابع به نام ()GetEnumerator تعریف میکند که خروجی آن کلاسی به نام Enumerator میباشد که این کلاس توابع و پراپرتی هایی متناسب با پیمایش 1 به 1 خانه های یک مجموعه را تعریف میکند.

مجموعه های مجازی:

حالا که با 2 روش دسترسی به یک مجموعه آشنا شدید, ما می بایست شما را با یک تعریف دیگر نیز به صورت خیلی کوتاه آشنا سازیم.

مجموعه های مجازی, همانند اسمشان, نشانگر یک مجموعه حقیقی در فضای حافظه نیستند! بلکه در واقع یک سری روش و دستور العمل برای تولید یک مجموعه حقیقی در حافظه میباشند.

مجموعه های مجازی موارد اصلی ای هستند که برای دسترسی به آن ها فقط از روش Enumeration میتوان استفاده کرد. چرا که خانه های این مجموعه ممکن است به صورت پیوسته حاضر نباشند و طول مجموعه به همین علت بدون انجام حداقل یک پیمایش نا مشخص میباشد!

بسیاری از توابع موجود در کتابخانه Linq از روش ایجاد مجموعه های مجازی استفاده میکنند و خروجی را به صورت یک IEnumerable به شما میدهند. در واقع دستور العمل ایجاد مجموعه حقیقی مورد نظر شما را بدون تولید مجموعه حقیقی و اختصاص فضای حافظه, در خود تولید کرده و در دسترس شما قرار میدهند.

این به این معنی است که شما میتوانید تصمیم بگیرید که مجموعه مجازی شما به یک مجموعه حقیقی در حافظه تبدیل بشود یا در حالت مجازی باقی بماند تا در صورت نیاز شما آن را پیمایش کرده, کار خود را انجام داده و بدون تخصیص حافظه اضافه ای آن را رها کنید.

خانه های یک مجموعه مجازی در واقع آدرس هایی برای دست یافتن به مقداری هستند که در مکانی دیگر از حافظه حضور دارند.
خانه های یک مجموعه مجازی در واقع آدرس هایی برای دست یافتن به مقداری هستند که در مکانی دیگر از حافظه حضور دارند.



حالا میتوانیم به موضوع اصلی بپردازیم!

صورت مسئله: یک مجموعه با تایپ IEnumerable داریم و این یعنی نمیدانیم که مجموعه پشت پرده از چه تایپی هست. در واقع راجب ماهیت مجموعه مورد نظر (حقیقی یا مجازی) اطلاع نداریم!

حالا ما برای انجام عملیاتی خاص به تعداد عناصر موجود در این مجموعه نیازمند هستیم.

خوب اگر شما حتی مقدار کمی تجربه در برنامه نویسی سی شارپ داشته باشید میگویید: "خوب اینکه خیلی آسون هست! از تابع ()Count موجود در کتابخانه Linq استفاده میکنیم!". و جواب حتی درست خواهد بود!

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

برای فهمیدن این موضوع باید نگاه کوتاهی به نحوه عملکرد تابع ()Count بیندازیم:

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

اما مشکل این رویکرد چه میباشد؟

همانطور که گفته شد, تابع ()Count در صورت شناسایی مجموعه ما به عنوان یک مجموعه مجازی, برای به دست آوردن طول آن تمامی عناصر آن را یکبار پیمایش میکند! شاید این جمله در ارقام و اندازه های کوچک تر از اهمیت بزرگی برخودار نباشد اما اگر مجموعه ما یک مجموعه بسیار حجیم از دیتا باشد چه؟

برای مثال: فرضا مجموعه ما یک مجموعه مجازی ساخته شده از 200 آرایه با اندازه های 500 تایی میباشد.

در صورت اعمال تابع ()Count ما باید 200 * 500 خانه حافظه را برای شمارش این مجموعه طی کنیم!

ولی همچنین ممکن است این شی cast شده ما یک مجموعه حقیقی باشد پس راه حل چیست؟

و اینجا شما را با تابع جدید کتابخانه Linq معرفی میکنم. این تابع با نام ()TryGetNonEnumeratedCount برای حل دقیقا همین مشکل ایجاد شده است.

IEnumerable<object> unknownList;
bool canEnumerate = unknownList.TryGetNonEnumeratedCount(out int count);

رویکرد این تابع در واقع دقیقا مشابه تابع قدیمی ()Count میباشد چرا که قدم اول آن دقیقا مانند آن تابع سعی میکند با cast کردن مجموعه ما به مجموعه های حقیقی ماهیت آن را شناسایی کند و در صورت موفقیت مقدار boolean درست را بازمیگرداند و مقدار طول را در پارامتر out خود به نام count میریزد و در صورت شناسایی مجموعه به عنوان یک مجموعه مجازی دست از کار برمیدارد و مقداری boolean غلط را به عنوان خروجی برمیگرداند.


امیدوار هستم که از خواندن این مقاله لذت برده باشید و دانش خود را گسترش داده باشید. تا نوشتار های بعدی
خدانگهدار.

- سپهر گل پذیر

نسخه فعلی مقاله: 1.0.1
برنامه نویسیسی شارپبرنامه نویسی بک اندcsharp
Software Engineer- Back-end .NET developer- interested in thinking
شاید از این پست‌ها خوشتان بیاید