المقابلة المتقدمة لـ C#: كيف تُبهر صاحب العمل ولا تفشل
\n\nالمقابلة لوظيفة مطور .NET Senior أو Middle ليست مجرد اختبار للقواعد النحوية. يرغب المحاورون في رؤية فهم عميق للعمل الداخلي للمنصة، والقدرة على كتابة كود عالي الأداء واتخاذ قرارات معمارية. في هذه المقالة، سنحلل الموضوعات والأسئلة الرئيسية للمستوى المتقدم التي ستساعدك على اجتياز المقابلة التقنية لـ C#.
\n\n1. البرمجة غير المتزامنة: async/await و Task
\n\nهذا هو الموضوع الأكثر شيوعًا في المقابلات على الأرجح. لا يكفي أن تقول إنك تعرف async/await. بل يجب أن تفهم كيف يعمل تحت الغطاء.
سؤال: ما هو SynchronizationContext وكيف يؤثر على تنفيذ await؟
\nسؤال كلاسيكي. SynchronizationContext هو تجريد يسمح بجدولة تنفيذ الكود في سياق معين (مثل تدفق واجهة المستخدم في WPF أو تجمع التدفقات في ASP.NET). عند استخدام await، بعد اكتمال العملية غير المتزامنة، يحاول الكود افتراضيًا العودة إلى السياق الأصلي. في تطبيقات الكونسول، هذا هو تجمع التدفقات، بينما في واجهة المستخدم هو تدفق المرسل (dispatcher). فهم هذه الآلية يساعد في تجنب حالات الجمود (deadlocks).
سؤال: ما الفرق بين Task.Run و Task.Factory.StartNew؟
\nللوهلة الأولى، يبدوان متشابهين، لكن Task.Run هو غلاف مبسط. Task.Factory.StartNew يوفر تحكمًا أكبر (مثل إمكانية تعيين TaskCreationOptions أو TaskScheduler). ومع ذلك، افتراضيًا يستخدم Task.Run TaskScheduler.Default (تجمع التدفقات)، بينما قد يستخدم Task.Factory.StartNew المجدول الحالي، مما يؤدي أحيانًا إلى سلوك غير متوقع.
// مثال على حالة جمود بسبب SynchronizationContext (في تطبيق واجهة مستخدم)\npublic async Task<string> GetDataAsync()\n{\n var result = await SomeAsyncMethod(); // التقاط السياق\n return result.ToString(); // يحاول العودة إلى تدفق واجهة المستخدم\n}\n\n// إذا تم استدعاء .Result أو .Wait() على المهمة، سيحدث deadlock\nvar data = GetDataAsync().Result; // سيء!\n\n\n2. إدارة الذاكرة وجامع القمامة (GC)
\n\nيجب على المطور المتقدم أن يفهم كيف يدير .NET الذاكرة لتجنب التسريبات وتحسين الأداء.
\n\nسؤال: اشرح أجيال GC (Gen 0, Gen 1, Gen 2) والكومة الكبيرة للكائنات (Large Object Heap - LOH).
\nيستخدم GC في .NET نموذجًا قائمًا على الأجيال. تدخل الكائنات أولاً إلى Gen 0 (يتم التجميع بشكل متكرر). إذا نجت من التجميع، تنتقل إلى Gen 1، ثم إلى Gen 2. LOH مخصص للكائنات التي يزيد حجمها عن 85,000 بايت. لا يتم ضغطه افتراضيًا (حتى .NET 4.5.1)، مما قد يسبب تجزئة. قد يكون السؤال أكثر تعقيدًا: "كيف يعمل تجميع القمامة الخلفي؟" أو "ما هي أوضاع GC: Workstation مقابل Server؟"
\n\nسؤال: ما هو الإنهاء (Finalization) وكيف تتجنب المشكلات المرتبطة به؟
\nالمنهيات (~Finalizer()) تبطئ تجميع القمامة، لأن الكائنات ذات المنهيات تدخل في قائمة انتظار الإنهاء وتتطلب تمريرتين من GC. النمط الصحيح هو تنفيذ IDisposable مع نمط Dispose.
public class ResourceHolder : IDisposable\n{\n private IntPtr _handle;\n private bool _disposed = false;\n\n public void Dispose()\n {\n Dispose(true);\n GC.SuppressFinalize(this);\n }\n\n protected virtual void Dispose(bool disposing)\n {\n if (!_disposed)\n {\n if (disposing)\n {\n // تحرير الموارد المُدارة\n }\n // تحرير الموارد غير المُدارة (handle)\n _disposed = true;\n }\n }\n\n ~ResourceHolder()\n {\n Dispose(false);\n }\n}\n\n\n3. المفوضون (Delegates)، الأحداث (Events) و LINQ: من القواعد النحوية إلى الفهم
\n\nهذه المواضيع غالبًا