C#'ta Dependency Injection (DI), bir sınıfın bağımlılıklarını (yani, dışarıdan alması gereken başka sınıfları veya bileşenleri) dışarıdan sağlayarak yönetilen bir tasarım desenidir. Bu desen, yazılım geliştirme sürecinde bağımlılıkların daha kolay yönetilmesine, test edilebilirliğin artmasına ve sınıfların daha esnek hale gelmesine olanak tanır.
C# ve .NET dünyasında Dependency Injection genellikle Inversion of Control (IoC) Container'lar ile yapılır. ASP.NET Core'da ise DI yerleşik olarak sunulmaktadır ve IoC container'ları ile kolayca entegre edilebilmektedir.
Dependency Injection Türleri
C#'ta DI üç farklı yöntemle yapılabilir:
- Constructor Injection (En yaygın yöntem)
- Property Injection
- Method Injection
1. Constructor Injection
En yaygın kullanılan DI yöntemidir. Sınıfın bağımlılıkları, constructor (yapıcı metot) aracılığıyla sağlanır. Böylece bağımlılıklar sınıfın başlatılması sırasında dışarıdan alınır ve private bir alan olarak saklanır.
Örnek:
Bir IService
arayüzüne bağımlılığı olan bir sınıf düşünelim:
public interface IService
{
void Serve();
}
public class Service : IService
{
public void Serve()
{
Console.WriteLine("Service is called.");
}
}
public class Client
{
private readonly IService _service;
// Dependency Injection via constructor
public Client(IService service)
{
_service = service;
}
public void Start()
{
Console.WriteLine("Client is starting...");
_service.Serve();
}
}
Yukarıdaki örnekte, Client
sınıfı bir IService
'e bağımlıdır. Bu bağımlılık, constructor ile dışarıdan sağlanmıştır. Service
sınıfı ise IService
arayüzünü implemente eder.
2. Property Injection
Bağımlılıkların özellikler (properties) aracılığıyla enjekte edilmesidir. Bu yöntem constructor injection kadar yaygın değildir ancak bazı durumlarda kullanışlı olabilir.
Örnek:
public class Client
{
public IService Service { get; set; }
public void Start()
{
Console.WriteLine("Client is starting...");
Service?.Serve();
}
}
Bu örnekte, Client
sınıfı IService
bağımlılığını bir property olarak alır. Ancak property injection, bağımlılıkların her zaman atanmasını garanti etmediği için constructor injection kadar güvenli değildir.
3. Method Injection
Bağımlılıkların bir metot aracılığıyla enjekte edilmesidir. Bu yöntem daha nadir kullanılır ve bağımlılıkların sadece belirli bir metot içinde gerekli olduğu durumlarda tercih edilir.
Örnek:
public class Client
{
public void Start(IService service)
{
Console.WriteLine("Client is starting...");
service.Serve();
}
}
Bu örnekte IService
bağımlılığı Start
metodu aracılığıyla enjekte edilmiştir.
ASP.NET Core ile Dependency Injection
ASP.NET Core framework'ü yerleşik bir Dependency Injection altyapısı sunar ve DI'yi kullanarak servisler tanımlamak oldukça kolaydır.
1. Servislerin Kayıt Edilmesi
ASP.NET Core'da servisler genellikle Startup.cs
veya .NET 6 ile Program.cs
dosyasında IoC container'a kaydedilir. Kayıt işlemi şu üç yaşam döngüsünden biriyle yapılır:
- Transient: Her seferinde yeni bir örnek oluşturulur.
- Scoped: Her istek (request) için bir örnek oluşturulur ve aynı istek içerisinde paylaşılır.
- Singleton: Uygulama yaşam döngüsü boyunca tek bir örnek oluşturulur.
Örnek:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Registering services
services.AddTransient<IService, Service>(); // Transient lifecycle
services.AddScoped<IMyScopedService, MyScopedService>(); // Scoped lifecycle
services.AddSingleton<ISingletonService, SingletonService>(); // Singleton lifecycle
}
}
2. Dependency Injection ile Kullanımı
Bir sınıfın bağımlılıklarını almak için, o sınıfın constructor'ında bu bağımlılıkları belirtmeniz yeterlidir. ASP.NET Core, ihtiyaç duyulan bağımlılıkları IoC container'dan sağlayacaktır.
Örnek:
public class HomeController : Controller
{
private readonly IService _service;
// Dependency Injection via constructor
public HomeController(IService service)
{
_service = service;
}
public IActionResult Index()
{
_service.Serve();
return View();
}
}
Yukarıdaki örnekte, HomeController
, IService
bağımlılığını constructor aracılığıyla alır ve bu bağımlılık IoC container tarafından otomatik olarak enjekte edilir.
DI ile Lifecycle Yönetimi
Her bir servis kaydı için belirli bir yaşam döngüsü tanımlayabilirsiniz:
- Transient: Servis her kullanıldığında yeni bir örnek oluşturulur. Kısa ömürlü servisler için uygundur.
- Scoped: Her istek başına bir örnek oluşturulur. Aynı HTTP isteği boyunca aynı servis kullanılır.
- Singleton: Uygulama boyunca yalnızca bir kez oluşturulur ve her seferinde aynı örnek kullanılır.
services.AddTransient<ITransientService, TransientService>();
services.AddScoped<IScopedService, ScopedService>();
services.AddSingleton<ISingletonService, SingletonService>();
Özet:
- Dependency Injection (Bağımlılık Enjeksiyonu) bağımlılıkların dışarıdan sağlandığı bir tasarım desenidir.
- C# ve ASP.NET Core'da DI en yaygın olarak Constructor Injection yöntemiyle yapılır.
- ASP.NET Core yerleşik bir DI altyapısı sunar ve servisler
Startup.cs
dosyasındaIServiceCollection
aracılığıyla kaydedilir. - Bağımlılıkların yaşam döngüleri Transient, Scoped ve Singleton olarak belirlenebilir.
Her ne kadar Singleton belirli durumlarda mantıklı bir tercih gibi görünse de, farklı yaşam döngüleri (Transient, Scoped, Singleton) farklı kullanım senaryoları için tasarlanmıştır. Farklı türlerin var olmasının ana nedeni, her yaşam döngüsünün farklı avantaj ve dezavantajları olmasıdır. İşte bu türlerin neden var olduğunu ve hangi durumlarda kullanıldığını daha iyi anlamanızı sağlayacak bazı açıklamalar:
1. Singleton
- Açıklama: Uygulama başladığında bir kez oluşturulur ve uygulamanın ömrü boyunca tek bir örnek kullanılır.
-
Ne zaman kullanılır?
- Stateless (durumsuz) ve paylaşımlı kaynaklar kullanan servislerde mantıklıdır. Örneğin, konfigürasyon ayarları, cache mekanizmaları, loglama servisleri veya veritabanı bağlantı havuzları gibi paylaşılan kaynaklar için ideal olabilir.
-
Avantajlar:
- Bellek kullanımını azaltır, çünkü her istek için yeni bir nesne oluşturulmaz.
- Uygulama ömrü boyunca aynı nesne tekrar tekrar kullanılır, bu da performansı artırabilir.
-
Dezavantajlar:
- Thread-safe (eşzamanlı erişime uygun) olmaları gerekir. Eğer servis bir durum tutuyorsa (stateful) ve çoklu thread'ler tarafından kullanılıyorsa dikkatli olunmalıdır, aksi halde veri tutarsızlıkları yaşanabilir.
- Paylaşılan veri veya bağımlılıklar arasında senkronizasyon sorunları yaşanabilir.
2. Transient
- Açıklama: Her istek yapıldığında yeni bir nesne oluşturulur. Servis kısa ömürlüdür ve her kullanımda yeni bir örnek sağlanır.
-
Ne zaman kullanılır?
- Durum tutan (stateful) ve her kullanımda izole bir nesneye ihtiyaç duyulan işlemlerde tercih edilir. Örneğin, kısa süreli işlemler, kullanıcıya özel verilerle çalışan işlemler, küçük hesaplamalar veya her işlemde temiz bir nesneye ihtiyaç duyulan yerlerde kullanılır.
-
Avantajlar:
- Her istek için yeni bir nesne oluşturulduğundan, önceki isteklerden kalan verilerle karışıklık yaşanmaz.
- Nesneler bir duruma (state) sahip oluyorsa, her işlemde temiz bir başlangıç yapılır.
-
Dezavantajlar:
- Daha fazla bellek ve işlemci gücü tüketir. Her istekte yeni bir nesne oluşturmak, özellikle büyük nesneler için maliyetli olabilir.
3. Scoped
- Açıklama: Her HTTP isteği (request) başına bir nesne oluşturulur ve bu nesne aynı istek süresince kullanılır. İstek tamamlandığında nesne yok edilir.
-
Ne zaman kullanılır?
- Kullanıcının veya isteğin belirli bir süre boyunca aynı servis örneğine ihtiyaç duyduğu durumlarda. Özellikle web uygulamaları ve ASP.NET Core projelerinde kullanıcının oturumu süresince bazı işlemleri izole şekilde gerçekleştiren servisler için uygundur. Örneğin, veritabanı işlemleri veya HTTP isteğiyle ilişkili işlemler için idealdir.
-
Avantajlar:
- Aynı istek boyunca aynı servis kullanılır, böylece veritabanı bağlantıları gibi kaynakların her seferinde tekrar tekrar oluşturulmasının önüne geçilir.
- Kullanıcının isteğine özel bir servis durumu (state) tutmak mümkündür.
-
Dezavantajlar:
- Bir HTTP isteği boyunca aynı servis kullanılır, bu da bazı senaryolarda verimsiz olabilir, ancak genellikle web uygulamaları için idealdir.
Neden Hepsi Birlikte Kullanılıyor?
Her bir yaşam döngüsünün farklı bir amacı vardır ve bir uygulamanın farklı ihtiyaçları olduğu için bunların tümü kullanılır:
- Singleton: Tek bir örneğin yeterli olduğu, global ve durumsuz (stateless) servislerde.
- Transient: Kısa ömürlü ve her işlemde bağımsız bir nesneye ihtiyaç duyulan yerlerde.
- Scoped: Özellikle web uygulamalarında, her kullanıcı isteği için bir nesneye ihtiyaç duyulan yerlerde.
Örneğin, bir web uygulamasında:
- Singleton'ı konfigürasyon ayarları veya loglama gibi global işler için kullanabilirsiniz.
- Transient'i her kullanıcı isteğinde yeni verilerle çalışan servisler için tercih edebilirsiniz.
- Scoped'u ise her HTTP isteği boyunca belirli bir veri bağlamında (context) çalışacak servisler için kullanabilirsiniz. Örneğin, her istek için aynı veritabanı bağlantısı kullanılabilir.
Özetle:
Singleton her durumda en mantıklı seçenek değildir, çünkü her kullanım senaryosunda ihtiyaçlar farklı olabilir. Farklı yaşam döngüleri uygulamanın esnekliğini artırır ve kaynak yönetimini daha verimli yapmanıza olanak tanır. Her servis için doğru yaşam döngüsünü seçmek performans, bellek kullanımı ve uygulamanın sağlıklı çalışması açısından kritik öneme sahiptir.
Top comments (0)