پوشش دقیق دامنههای آزمون
این بانک سوالات تمرینی دقیقاً بر اساس دامنههای اصلی مصاحبههای فنی مدرن C# و .NET، بررسیهای معماری و ارزیابیهای توسعهدهندگان ارشد ساختار یافته است:
مبانی زبان (۲۰٪)
مباحث پوشش داده شده: انواع دادههای پیشرفته، انواع Value در مقابل Reference، امضاهای متد، زنجیرهسازی سازندهها، آرایههای چندبعدی و دندانهدار، و مدیریت بهینه رشتهها با استفاده از ReadOnlySpan و StringBuilder.
برنامهنویسی شیگرای (۱۸٪)
مباحث پوشش داده شده: مکانیسمهای عمیق اجرای ارثبری، پلیمورفیسم زمان اجرا در مقابل زمان کامپایل، کلاسهای انتزاعی در مقابل اینترفیسها، پیادهسازی صریح اینترفیس، مرزهای کپسولهسازی و اصلاحکنندههای دسترسی.
مجموعهها و LINQ (۱۵٪)
مباحث پوشش داده شده: مکانیسمهای داخلی List، Dictionary و HashSet، مقایسهکنندههای برابری سفارشی، سینتکس Query در مقابل Method در LINQ، اجرای تعویقی و بهینهسازی عبارتهای لامبدا برای سرعت و کاهش اثرات تخصیص حافظه.
برنامهنویسی ناهمگام (۱۲٪)
مباحث پوشش داده شده: ماشین حالت async/await، بهرهبرداری از Task و ValueTask، استفاده از Thread Pool، کاربرد صحیح ConfigureAwait، اجتناب از تلههای async void و مدیریت Deadlockها در محیطهای همگامسازی قدیمی.
تعامل با پایگاه داده و Entity Framework (۱۲٪)
مباحث پوشش داده شده: معماری Entity Framework Core، نوشتن کوئریهای بهینه LINQ-to-Entities، مدیریت وضعیتهای Change Tracking، طراحی صریح الگوهای Lazy Loading در مقابل Eager Loading و رفع مشکل N+1.
مدیریت خطا و دیباگ (۱۰٪)
مباحث پوشش داده شده: ساختاربندی بلوکهای تمیز Try/Catch/Finally، مکانیسمهای فیلترینگ Exception، مدیریت انواع خطاهای سیستمی سفارشی، کپچرینگ تشخیصها، تحلیل Stack Trace و تکنیکهای دیباگ Memory Dump.
مدیریت حافظه و کارایی (۸٪)
مباحث پوشش داده شده: نسلهای Garbage Collection (Gen 0, 1, 2)، دینامیکهای Large Object Heap (LOH)، الگوی IDisposable و Finalizerها، تخصیص Stack در مقابل Heap، انواع دادههای Nullable و قوانین Nullable Reference Types.
الگوهای طراحی و معماری (۵٪)
مباحث پوشش داده شده: پیادهسازی موثر الگوی MVC، الگوهای Repository و Unit of Work، چرخه عمر Dependency Injection (Transient, Scoped, Singleton) و همسویی سرویسها در معماریهای سرویسگرا (SOA) یا محیطهای میکروسرویس.
توضیحات دوره
موفقیت در یک مصاحبه فنی مدرن C# یا .NET نیازمند چیزی بسیار فراتر از تکرار تعاریف یا نوشتن حلقههای کتابخانهای است. شرکتهایی که کاندیداها را برای نقشهای سطح متوسط، ارشد یا معماری ارزیابی میکنند، به دنبال بلوغ مهندسی هستند. آنها متخصصانی را میخواهند که اجرای زمان اجرا (Runtime)، اثرات حافظه، بهینهسازی تردها و الگوهای طراحی معماری تمیز را درک کنند.
من این بانک جامع سوالات تمرینی را ایجاد کردم تا ابزاری کامل برای تست مرزهای فنی واقعی شما باشد. با ۵۵۰ سوال سناریویی با کیفیت و سطح فنی بالا، این دوره دقیقاً منعکسکننده استانداردهای فعلی مصاحبههای فنی در شرکتهای تراز اول تکنولوژی و تیمهای نرمافزاری سازمانی است.
به جای سوالات پیشپاافتاده درباره سینتکس، با پازلهای مهندسی دنیای واقعی روبرو خواهید شد. شما نشتهای حافظه را در Garbage Collector تشخیص میدهید، Deadlockها را در استکهای فراخوانی ناهمگام دیباگ میکنید، رفتوبرگشتهای دیتابیس در EF Core را بهینه میکنید و سلسلهمراتبهای شکننده شیگرایی را بازنویسی میکنید. هر سوال شامل یک تحلیل دقیق است که توضیح میدهد چرا یک انتخاب مهندسی درست است و چرا رویکردهای جایگزین در محیطهای عملیاتی شکست میخورند. با تمرین این ارزیابیهای هدفمند، رفلکسهای تحلیلی لازم را ایجاد میکنید تا با اعتماد به نفس کامل وارد مصاحبه شوید و جایگاه مهندسی مورد نظرتان را به دست آورید.
نمونهای از سوالات تمرینی
سوال ۱: برنامهنویسی ناهمگام و کانتکستهای تردینگ
یک توسعهدهنده ارشد در حال بازنویسی یک سرویس پردازش بکاند با ترافیک بالا در .NET Core است. در حین بازنویسی، او متدی داخلی برای اجرای وظایف پاکسازی پسزمینه مینویسد و از امضای async void به جای async Task استفاده میکند. اگر در حین اجرا، یک Exception مدیریتنشده در این متد async void رخ دهد، رفتار دقیق Runtime برنامه چگونه خواهد بود؟
الف) Exception به صورت graceful توسط Runtime کپچر شده و زمانی که ترد فراخوان سعی در await کردن متد دارد، دوباره پرتاب میشود.
چرا غلط است: یک متد async void نمیتواند await شود زیرا چیزی به فراخوان باز نمیگرداند. کد فراخوان هیچ هندل یا ارجاع Task برای ردیابی اجرا ندارد، بنابراین کپچر یا پرتاب مجدد Exception به این روش غیرممکن است.
ب) Exception به صورت بیصدا توسط محیط اجرا سرکوب شده و به برنامه اجازه میدهد بدون هیچ لاگی به کار خود ادامه دهد.
چرا غلط است: Runtime داتنت خطاهای مدیریتنشده در متدهای async void را نادیده نمیگیرد. سرکوب خطاهای بحرانی بدون اطلاعرسانی باعث فساد پیشبینیناپذیر در وضعیت برنامه میشود.
ج) Exception به طور خودکار به رویداد TaskScheduler.UnobservedTaskException هدایت شده و تا زمانی که یک هندلر جهانی ثبت شده باشد، از کرش کردن پروسه جلوگیری میکند.
چرا غلط است: این رویداد مخصوص تسکهایی است که یک شیء Task برمیگردانند اما Exception آنها مشاهده نشده است. چون async void هیچ Taskی تولید نمیکند، این رویداد هرگز برای آن اجرا نمیشود.
د) Exception مستقیماً روی SynchronizationContext فعال در زمان شروع متد ایجاد شده و پروسه برنامه را فوراً کرش میدهد.
چرا درست است: چون متد async void هیچ Taskی برای ارتباط وضعیت چرخه عمرش برنمیگرداند، هر Exception مدیریتنشده در آن نمیتواند توسط بلوک try-catch پیرامون فراخوانی کپچر شود. Runtime در عوض خطا را مستقیماً به SynchronizationContext جاری میفرستد. اگر کانتکست سفارشی وجود نداشته باشد، خطا به ترد استخر (Thread Pool) میرود و کل پروسه برنامه را فوراً متوقف میکند.
ه) CLR شکست در اجرا را متوقف کرده و را مجبور میکند امضای متد به صورت آنی به یک نمایش internal async Task تبدیل شود تا خطا را ایمن پردازش کند.
چرا غلط است: CLR و کامپایلر، ماشین حالت ناهمگام را در زمان کامپایل بر اساس امضاهای صریح کد میسازند. Runtime هرگز امضاهای متادیتای کامپایل شده را در حین اجرا به صورت پویا تغییر نمیدهد.
و) ترد فراخوان در نقطه فراخوانی اولیه به طور نامحدود بلاک شده و منتظر سیگنال timeout داخلی از thread pool میماند.
چرا غلط است: یک فراخوانی متد ناهمگام، کنترل را بلافاصله در اولین عبارت await که تسلیم (yield) شود به فراخوان برمیگرداند و باعث فریز شدن یا انتظار برای timeout در ترد فراخوان نمیشود.
سوال ۲: مدیریت حافظه و بهینهسازی پیشرفته Heap
یک برنامه معاملاتی مالی با تراکنش بالا در C# میلیونها بسته داده کوچک تخصیص میدهد که سریعاً پردازش و دور ریخته میشوند. به طور همزمان، مرتباً بافرهای کش بزرگی را به صورت آرایههای byte با اندازه بیش از ۹۰,۰۰۰ بایت مقداردهی میکند. Garbage Collector داتنت چگونه با این تخصیصهای متمایز برخورد میکند و معمار سیستم باید مراقب کدام آسیبپذیری اصلی در عملکرد باشد؟
الف) هم بستههای داده کوچک و هم آرایههای byte بزرگ در نسل ۰ (Generation 0) تخصیص مییابند و در یک sweep واحد جمعآوری زباله، به صورت همزمان فشرده (Compact) میشوند.
چرا غلط است: اشیاء بزرگ هرگز در نسل ۰ قرار نمیگیرند. فشردهسازی بلوکهای بزرگ حافظه به دلیل هزینه کپیبرداری بالا، بسیار گران است، بنابراین Runtime آنها را فوراً جداسازی میکند.
ب) بستههای کوچک در نسل ۰ تخصیص مییابند، در حالی که هر آرایهای بیش از ۸۵,۰۰۰ بایت مستقیماً به Large Object Heap (LOH) هدایت میشود. چون LOH به طور پیشفرض در جمعآوریهای استاندارد فشرده نمیشود، برنامه در معرض خطر شدید Fragmentation حافظه و خطای OutOfMemoryException قرار دارد.
چرا درست است: اشیاء بزرگتر از ۸۵,۰۰۰ بایت از لایههای جمعآوری نسلی (Gen 0, Gen 1) عبور کرده و مستقیماً در LOH قرار میگیرند. LOH با این تخصیصها به عنوان بخشی از نسل ۲ برخورد میکند. نکته حیاتی این است که LOH برای صرفهجویی در CPU در طول sweeps استاندارد فشرده نمیشود. با گذشت زمان، تخصیص و آزادسازی آرایههای بزرگ با اندازههای متفاوت، شکافهای ساختاری در فضای آدرس Heap ایجاد میکند که منجر میشود برنامه با وجود داشتن حافظه آزاد کلی، فضای متصل (Contiguous) برای تخصیص جدید نداشته باشد.
ج) سیستم به طور خودکار تمام آرایههای byte بزرگ را به Heap غیرمدیریتی سیستمعامل منتقل میکند که نیازمند آزادسازی دستی اشارهگرها با استفاده از بلوکهای unsafe است.
چرا غلط است: آرایههای byte در C# حتی در اندازه بزرگ، اشیاء مدیریتشده (Managed) هستند و در محدوده Heap مدیریتشده توسط GC قرار دارند؛ بنابراین توسعهدهندگان از مدیریت دستی اشارهگرها استفاده نمیکنند.
د) Garbage Collector موتور اجرا را قفل کرده و LOH را بعد از هر بار جمعآوری نسل ۱ فشرده میکند تا تضمین شود هیچ Fragmentation ساختاری وجود ندارد.
چرا غلط است: فشردهسازی LOH نیازمند یک جمعآوری کامل نسل ۲ است و عملیاتی سنگین است. این کار هرگز بعد از یک جمعآوری سبک نسل ۱ انجام نمیشود زیرا عملکرد Real-time را تخریب میکند.
ه) انواع Value موجود در آرایههای بزرگ جدا شده و مستقیماً روی Stack ترد فعال تخصیص مییابند و کاملاً Heap مدیریتشده را دور میزنند.
چرا غلط است: در حالی که انواع Value به طور عادی روی Stack قرار میگیرند، هر Value Type که داخل یک آرایه باشد، بخشی از ساختار حافظه آن شیء آرایه میشود. چون آرایهها Reference Type هستند، کل بلوک روی Heap قرار میگیرد.
و) Runtime از الگوریتمهای هوش مصنوعی پیشبین برای شناسایی آرایههای بزرگ استفاده کرده و آدرس حافظه آنها را برای همیشه Pin میکند تا هرگز جمعآوری نشوند.
چرا غلط است: اشیاء در LOH زمانی که دیگر ارجاع فعالی نداشته باشند، کاملاً قابل جمعآوری هستند. Pin کردن حافظه مانع از جابجایی شیء میشود، اما مانع از جمعآوری آن پس از تبدیل شدن به زباله نمیشود.
سوال ۳: Entity Framework Core و مکانیسمهای بازیابی داده
یک توسعهدهنده از EF Core برای دریافت دادهها از یک دیتابیس رابطهای استفاده میکند. او یک کوئری LINQ مینویسد تا مجموعهای از موجودیتهای Parent را بگیرد و سپس مجموعههای Child مرتبط را بر اساس شرایط تجاری خاص فیلتر کند. توسعهدهنده به اشتباه متد .ToList() را بلافاصله بعد از DbSet مربوط به Parents و قبل از اعمال منطق فیلترینگ Child قرار میدهد. نتیجه معماری دقیق این جایگذاری چیست؟
الف) EF Core به طور هوشمند کل Expression Tree را تحلیل کرده و اجرای کوئری را به تعویق میاندازد تا بتواند فیلتر نهایی Child را به SQL Server بفرستد.
چرا غلط است: متد .ToList() به عنوان یک نقطه اجرای نهایی و فوری عمل میکند. این متد توانایی EF Core را برای تحلیل عبارتهای بعدی میگیرد زیرا مجبور میکند کوئری در همان لحظه کامپایل و در دیتابیس اجرا شود.
ب) موتور دیتابیس با یک خطای ارزیابی مواجه شده و باعث حذف و ایجاد مجدد (Drop-and-Recreate) تخریبی در شمای جدولهای هدف میشود.
چرا غلط است: جایگذاری اشتباه یک اپراتور LINQ باعث مدیریت ناکارآمد دادهها یا کرش برنامه میشود، اما هرگز دستورات DDL که جدولها را تغییر دهد یا حذف کند، صادر نمیکند.
ج) کل جدول دیتابیس رکوردهای Parent ابتدا در حافظه برنامه بارگذاری میشود (Client-side Evaluation) که باعث مصرف بالای حافظه و انتقال حجم عظیمی از دادههای فیلتر نشده در شبکه میشود.
چرا درست است: در EF Core، فراخوانی .ToList() باعث متریالیزه شدن فوری کوئری میشود. با فراخوانی آن مستقیماً روی مجموعه داده Parents قبل از اعمال هرگونه فیلتر، برنامه به EF Core دستور میدهد تمام ردیفهای آن جدول را از شبکه کشیده و به اشیاء در حافظه تبدیل کند. هرگونه فیلترینگ بعدی کاملاً در ماشین محلی (سمت کلاینت) اتفاق میافتد که باعث بیفایده شدن Indexهای دیتابیس و ایجاد یک گلوگاه عملکردی شدید میشود.
د) یک Exception کامپایل زمان اجرای شدید فوراً پرتاب میشود زیرا کامپایلر نمیتواند متد .ToList() را روی یک مجموعه خام IQueryable دیتابیس اجرا کند.
چرا غلط است: فراخوانی .ToList() روی IQueryable کاملاً از نظر سینتکسی درست است و روش اصلی توسعهدهندگان برای دستور اجرای کوئری و دریافت نتایج ملموس است.
ه) Lazy loading باعث ایجاد رفتوبرگشتهای شبکه SQL مجزا و ایزوله میشود تا مقدار هر ستون را گامبهگام برای هر ردیف بازگشتی دریافت کند.
چرا غلط است: جایگذاری اشتباه .ToList() ساختارهای کامل ردیف را فوراً به حافظه میآورد. این کار باعث Lazy loading ستون-به-ستون نمیشود، هرچند اگر ویژگیهای Navigation فرزند بعداً خارج از دستور Eager Load دسترسی پیدا کنند، میتواند Lazy loading ردیف-به-ردیف را فعال کند.
و) وضعیت Tracking کانتکست دیتابیس به طور بازگشتناپذیر به تنظیمات Immutable تغییر کرده و هرگونه تغییر داده در رکوردهای بازیابی شده را مسدود میکند.
چرا غلط است: متریالیزه کردن موجودیتها با .ToList() آنها را به عنوان اشیاء کاملاً Track شده در DbContext نگه میدارد مگر اینکه پیکربندی صریحی (مانند .AsNoTracking()) وجود داشته باشد. این کار معماری Tracking کانتکست را تغییر نمیدهد.
به آزمونهای سوالات مصاحبهای خوش آمدید تا شما را برای ارزیابی سوالات مصاحبه C# آماده کنیم.
شما میتوانید هر تعداد بار که بخواهید در آزمونها شرکت کنید.
این یک بانک سوالات عظیم و اورجینال است.
اگر سوالی داشته باشید، از پشتیبانی مدرسان بهرهمند میشوید.
هر سوال دارای یک توضیح دقیق است.
با اپلیکیشن Udemy سازگار با موبایل است.
امیدوارم تا الان متقاعد شده باشید! سوالات بسیار بیشتری در داخل دوره وجود دارد.
Interview Questions Tests
مربی در Udemy
نمایش نظرات