مقدمة: لماذا تعتبر أداء لغة C# مهمًا
تعتبر لغة C# ومنصة .NET أدوات قوية لبناء خدمات الويب عالية التحميل، وتطبيقات سطح المكتب، والألعاب على Unity. ومع ذلك، حتى الكود الأكثر أناقة يمكن أن يعمل ببطء إذا لم يتم مراعاة خصائص عمل CLR (Common Language Runtime)، وجامع القمامة (GC)، وآليات الترجمة في الوقت المناسب (JIT).
في هذه المقالة، سنقوم بتحليل الطرق الرئيسية لتحسين أداء تطبيقات C# والتي ستساعدك على تقليل وقت الاستجابة، وتقليل استهلاك الذاكرة، وزيادة سعة النظام. جميع النصائح مصحوبة بأمثلة عملية للكود.
1. إدارة الذاكرة وجامع القمامة (GC)
واحدة من الميزات الرئيسية لـ .NET هي الإدارة التلقائية للذاكرة. لكن الاستخدام غير الصحيح للكائنات يؤدي إلى عمليات جمع قمامة متكررة، مما يسبب "تجميد" (pause) التطبيق.
1.1. تجنب تخصيص الذاكرة في المسارات الساخنة
حاول ألا تنشئ كائنات جديدة داخل الحلقات أو الطرق التي يتم استدعاؤها بشكل متكرر. بدلاً من ذلك، استخدم مجموعات الكائنات أو أعد استخدام النسخ.
// سيء: يتم إنشاء StringBuilder جديد في كل تكرار
for (int i = 0; i < 1000; i++)
{
var sb = new StringBuilder();
sb.Append(i);
}
// جيد: نستخدم مجموعة أو نعيد الاستخدام
var sbPool = new StringBuilder();
for (int i = 0; i < 1000; i++)
{
sbPool.Clear();
sbPool.Append(i);
}
1.2. استخدم الهياكل (struct) بدلاً من الفئات (class) للبيانات الصغيرة
يتم تخزين الهياكل (struct) على المكدس ولا تخلق عبئًا على GC. لكن كن حذرًا: الهياكل الكبيرة (أكثر من 16 بايت) قد يتم نسخها ببطء.
public struct Point
{
public int X;
public int Y;
}
// الآن استدعاء الطريقة لا ينشئ كائنًا في الكومة
void ProcessPoint(Point p) { /* ... */ }
1.3. قم بتفعيل وضع Server GC للتطبيقات الخادمة
افتراضيًا، تستخدم تطبيقات وحدة التحكم وضع Workstation GC. بالنسبة لخدمات الويب (ASP.NET Core)، قم بالتبديل إلى Server GC في ملف csproj:
<PropertyGroup>
<ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>
2. تحسين العمل مع LINQ والمجموعات
LINQ هي أداة مريحة ولكنها قد تكون بطيئة إذا لم تكن على دراية بـ "مزالقها".
2.1. تجنب التعدادات المتعددة
كل استدعاء لـ .Count() أو .ToList() أو .Any() قد يعيد تنفيذ الاستعلام. قم بحفظ النتيجة في متغير.
// سيء: تمريرين على المجموعة
if (items.Where(x => x.IsActive).Count() > 0) { /* ... */ }
var activeList = items.Where(x => x.IsActive).ToList();
// جيد: تمرير واحد
var activeItems = items.Where(x => x.IsActive).ToList();
if (activeItems.Count > 0) { /* ... */ }
2.2. استخدم المجموعات المتخصصة
للبحث المتكرر عن مفتاح، استخدم Dictionary<TKey, TValue> بدلاً من List<T>. لعمليات "أول من يدخل - أول من يخرج"، استخدم Queue<T>.
// بحث سريع: O(1) بدلاً من O(n)
var dict = new Dictionary<int, string>();
dict.Add(1, "one");
if (dict.ContainsKey(1)) { /* ... */ }
2.3. استبدل LINQ بالحلقات في الأقسام الحرجة
بالنسبة للعمليات البسيطة (الجمع، التصفية)، يمكن أن تكون حلقة for أو foreach أسرع بمقدار 2-3 مرات من LINQ.
// LINQ (بطيء)
int sum = numbers.Sum();
// حلقة (سريع)
int sum = 0;
for (int i = 0; i < numbers.Length; i++)
{
sum += numbers[i];
}
3. البرمجة غير المتزامنة وتقليل الحظر
الاستخدام الصحيح لـ async/await يسمح بعدم حظر الخيوط، وهو أمر بالغ الأهمية لقابلية التوسع.