AddTransient, AddScoped, AddSingleton: الاختلافات في C# مع أمثلة

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

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

ابدأ الدورة

AddTransient, AddScoped و AddSingleton: كيف لا تحتار في دورات حياة خدمات C#

\n\n

هل سبق لك أن أمسكت بنفسك تفكر: "لماذا تحتفظ خدمتي بالبيانات بين الطلبات، على الرغم من أنني لم أطلب ذلك؟" أو العكس - "لماذا يتم إنشاء كائن جديد في كل مرة، وأفقد الحالة؟"

\n\n

مرحباً بك في عالم حقن التبعيات (Dependency Injection, DI) في ASP.NET Core! هذا هو أحد أقوى المواضيع وأكثرها إرباكاً للمبتدئين في نفس الوقت. اليوم سنشرح الأركان الثلاثة التي يقوم عليها إدارة الكائنات في C#: AddTransient و AddScoped و AddSingleton.

\n\n

في نهاية هذا المقال، لن تفهم الفرق فحسب، بل ستتمكن أيضاً من الإجابة بثقة على هذا السؤال في المقابلات (نعم، يحبون طرحه!). فلنبدأ!

\n\n

ما هي دورة حياة الخدمة ولماذا نحتاجها؟

\n\n

تخيل أن تطبيقك هو مطعم. الخدمات هي الطهاة والنُدُل والأطباق. دورة الحياة تحدد مدة "بقائهم" على قيد الحياة:

\n\n
    \n
  • Transient (عابر) - موظف لمرة واحدة. يأتي، يعد طبق حساء واحد ويغادر. طلب جديد يعني موظف جديد.
  • \n
  • Scoped (ضمن نطاق) - موظف لنوبة عمل واحدة. يخدم جميع العملاء طالما أن المطعم مفتوح (طالما أن طلب HTTP مستمر).
  • \n
  • Singleton (فردي) - الشيف الرئيسي الذي يعمل طوال عمر المطعم. واحد للتطبيق بأكمله.
  • \n
\n\n

في C#، يتم تنفيذ هذا المنطق من خلال حاوية DI المدمجة. دعنا نلقي نظرة على الكود.

\n\n

1. AddTransient - مثيل جديد في كل مرة

\n\n

متى تستخدم: خدمات خفيفة بدون حالة (stateless) لا تخزن بيانات بين الاستدعاءات. مثالية للمستودعات (Repositories) والمساعدين (Helpers) والأدوات المساعدة (Utilities).

\n\n

كيف تعمل: في كل مرة تطلب فيها الخدمة (من خلال المُنشئ أو مباشرة من الحاوية)، يتم إنشاء كائن جديد.

\n\n
// التسجيل في Program.cs\nbuilder.Services.AddTransient<IMyService, MyService>();\n\n// مثال على الخدمة\npublic interface IMyService\n{\n    Guid GetInstanceId();\n}\n\npublic class MyService : IMyService\n{\n    private readonly Guid _id = Guid.NewGuid();\n    public Guid GetInstanceId() => _id;\n}\n\n// المتحكم الذي يستخدم الخدمة\npublic class HomeController : Controller\n{\n    private readonly IMyService _service1;\n    private readonly IMyService _service2;\n\n    public HomeController(IMyService service1, IMyService service2)\n    {\n        _service1 = service1;\n        _service2 = service2;\n    }\n\n    public IActionResult Index()\n    {\n        ViewBag.Id1 = _service1.GetInstanceId();\n        ViewBag.Id2 = _service2.GetInstanceId();\n        // Id1 و Id2 GUIDs مختلفان!\n        return View();\n    }\n}\n
\n\n

مهم: حتى ضمن طلب HTTP واحد، إذا قمت بحقن IMyService في مكانين مختلفين، ستحصل على كائنين مختلفين. يتم استهلاك الذاكرة، ولكن لا يوجد خطر "تلوث" البيانات.

\n\n

2. AddScoped - مثيل واحد لكل طلب

\n\n

متى تستخدم: الخدمات التي يجب أن تكون متسقة ضمن طلب HTTP واحد. على سبيل المثال، DbContext في Entity Framework Core، خدمات التعامل مع سلة التسوق، مسجلات الطلبات.

\n\n

كيف تعمل: ضمن طلب HTTP واحد (أو نطاق scope) يتم إنشاء مثيل واحد. إذا طلبتها عدة مرات - تحصل على نفس الكائن. لكن كل طلب جديد من مستخدم آخر سيحصل على مثيله الخاص.

\n\n
// التسجيل\nbuilder.Services.AddScoped<IScopedService, ScopedService>();\n\npublic interface IScopedService\n{\n    Guid GetInstanceId();\n}\n\npublic class ScopedService : IScopedService\n{\n    private readonly Guid _id = Guid.NewGuid();\n    public Guid GetInstanceId() => _id;\n}\n\n// في نفس المتحكم\npublic                

المدونات

توصيات الكتب