Distinct حسب حقل واحد في C#: كيفية إزالة التكرارات من الكائنات

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

المشكلة: Distinct() لا يعرف التصفية حسب الحقل

\n\n

تخيل: لديك قائمة بالعملاء، وفيها تكرارات حسب البريد الإلكتروني. تكتب:

\n\n
var uniqueClients = clients.Distinct().ToList();
\n\n

وتتفاجأ بأن التكرارات لم تختفِ. لماذا؟ لأن Distinct() القياسي يقارن الكائنات كاملة حسب المرجع (أو حسب Equals إذا قمت بإعادة تعريفه). إنه لا يعلم أن "التفرد" بالنسبة لك هو Email فريد، وليس نسخة فريدة من الكلاس.

\n\n

هذا السؤال هو من أكثر الأسئلة شيوعاً على Stack Overflow حول LINQ (أكثر من 500 ألف مشاهدة). واليوم سنشرح 3 طرق عملية لحل هذه المشكلة في C#. هيا بنا!

\n\n

الطريقة 1: GroupBy + First (الكلاسيكية)

\n\n

أبسط وأوضح طريقة، ولا تتطلب تثبيت مكتبات خارجية. الفكرة: تجميع الكائنات حسب الخاصية المطلوبة، ثم أخذ أول عنصر من كل مجموعة.

\n\n
var uniqueByEmail = clients\n    .GroupBy(c => c.Email)\n    .Select(g => g.First())\n    .ToList();
\n\n

كيف يعمل:

\n
    \n
  • GroupBy(c => c.Email) — يقسم المجموعة إلى مجموعات فرعية، حيث المفتاح هو البريد الإلكتروني.
  • \n
  • Select(g => g.First()) — يأخذ أول كائن من كل مجموعة فرعية.
  • \n
  • يتم تجاهل التكرارات المتبقية.
  • \n
\n\n

الإيجابيات:

\n
    \n
  • يعمل "مباشرة من الصندوق" (فقط using System.Linq;).
  • \n
  • مفهوم بديهياً حتى للمبتدئين.
  • \n
  • يحافظ على الترتيب الأصلي للعناصر (يبقى أول كائن تم العثور عليه).
  • \n
\n\n

السلبيات:

\n
    \n
  • أبطأ قليلاً على البيانات الكبيرة (يحتاج لإنشاء مجموعات).
  • \n
  • الكود أطول قليلاً مما نرغب.
  • \n
\n\n

الطريقة 2: MoreLinq و DistinctBy (الأناقة)

\n\n

إذا كنت لا تمانع المكتبات الخارجية، فإن MoreLinq تقدم طريقة مثالية DistinctBy. تقوم بالضبط بما تتوقعه من Distinct — التصفية حسب الخاصية.

\n\n

التثبيت عبر NuGet:

\n
Install-Package MoreLinq
\n\n

الاستخدام:

\n
using MoreLinq;\n\nvar uniqueByEmail = clients.DistinctBy(c => c.Email).ToList();
\n\n

سطر واحد فقط وكل شيء جاهز. داخلياً، تستخدم DistinctBy HashSet وتعمل بتعقيد O(n).

\n\n

الإيجابيات:

\n
    \n
  • كود موجز وقابل للقراءة للغاية.
  • \n
  • أداء عالٍ.
  • \n
  • المكتبة مختبرة جيداً (أكثر من 10 ملايين تحميل).
  • \n
\n\n

السلبيات:

\n
    \n
  • تحتاج لإضافة تبعية للمشروع.
  • \n
  • ليست كل الفرق تسمح بالمكتبات الخارجية.
  • \n
\n\n

الطريقة 3: IEqualityComparer مخصص (التحكم الكامل)

\n\n

هذه الطريقة مناسبة عندما لا تريد إضافة مكتبة، ولكنك تريد إعادة استخدام منطق المقارنة في عدة أماكن. ننشئ المقارن الخاص بنا:

\n\n
public class ClientEmailComparer : IEqualityComparer<Client>\n{\n    public bool Equals(Client x, Client y)\n    {\n        if (ReferenceEquals(x, y)) return true;\n        if (x is null || y is null) return false;\n        return x.Email == y.Email;\n    }\n\n    public int GetHashCode(Client obj)\n    {\n        return obj.Email?.GetHashCode() ?? 0;\n    }\n}
\n\n

الاستخدام:

\n
var comparer = new ClientEmailComparer();\nvar uniqueByEmail = clients.Distinct(comparer).ToList();
\n\n

الإيجابيات:

\n
    \n
  • تحكم كامل في منطق المقارنة.
  • \n
  • يمكن إعادة استخدام المقارن في Contains و Except و Union وغيرها.
  • \n
  • لا يتطلب مكتبات خارجية.
  • \n
\n\n

السلبيات:

\n
    \n
  • الكثير من الكود النمطي.
  • \n

المدونات

توصيات الكتب