Berk Akademi
Birebir ders başvurusu Ücretsiz ön görüşme Ana Sayfa

C# "Cannot access a disposed object." Hatası Nasıl Çözülür?

csharp-cannot-access-a-disposed-object-hatasi-nasil-cozulur
Bu yazıda neler var?
  1. ObjectDisposedException hatası ne anlama gelir?
  2. Oluştur, kullan, kapat: Nesne sahipliği kontrol listesi
  3. using, Stream ve dosya işlemlerinde hata nasıl düzeltilir?
  4. HttpClient ve DbContext için sahiplik nasıl belirlenir?
  5. Stack trace ile üç adımda ObjectDisposedException teşhisi
  6. Sık Sorulan Sorular

C# “Cannot access a disposed object.” hatası, daha önce Dispose() işleminden geçirilmiş bir nesnenin yeniden kullanılmaya çalışıldığını gösterir. Çözüm, Dispose() çağrısını gelişigüzel kaldırmak değil, nesnenin oluşturulması, kullanılması ve kapatılması arasındaki yaşam süresini uyumlu hâle getirmektir.

Hata mesajında görünen nesne adı, teşhisin başlangıç noktasıdır. Bu ad, kapanmış bir akışın, dosya okuyucusunun, istemcinin veya başka bir kaynağın kullanım sırasında devre dışı kaldığını anlamana yardımcı olabilir.

ObjectDisposedException hatası ne anlama gelir?

ObjectDisposedException, bir nesne kapatıldıktan veya kaynakları serbest bırakıldıktan sonra o nesne üzerinde işlem yapılmaya çalışıldığında oluşur. Örneğin bir Stream nesnesi Dispose() ile kapatıldıktan sonra Read, Write ya da Length gibi özellik ve metotlara erişmek bu hatayı doğurabilir.

Dispose() çağrısı, nesnenin artık kullanılmayacağını belirtir. Dosya tanıtıcıları, ağ bağlantıları, bellek akışları veya veritabanı bağlantıları gibi kaynakların düzenli biçimde serbest bırakılması için önemlidir. Bu nedenle hatayı görünce ilk tepki olarak bütün Dispose() çağrılarını kaldırmak doğru değildir. Böyle bir değişiklik, bu kez kaynakların gereğinden uzun süre açık kalmasına yol açabilir.

Mesajdaki nesne adı veya istisnanın ObjectName bilgisi varsa bunu not et. Örneğin mesaj bir akışın kapatıldığını gösteriyorsa, akışı hangi metodun oluşturduğunu ve hangi kapsamda kapattığını incele. Microsoft Learn ObjectDisposedException belgeleri, bu istisnanın kapatılmış nesne üzerinde işlem yapılmasıyla ilişkili olduğunu ve nesne adının teşhiste kullanılabildiğini açıklar.

Oluştur, kullan, kapat: Nesne sahipliği kontrol listesi

Bir nesnenin yaşam döngüsünü anlamak için üç aşamayı birlikte incele: nesne nerede oluşturuldu, hangi kod tarafından kullanılıyor ve kapatma sorumluluğu kime ait? Bu sorular yanıtlanmadan yapılan değişiklik, hatayı başka bir noktaya taşıyabilir.

Kontrol sorusu İncelenecek işaret Olası düzeltme
Nesneyi kim oluşturdu? new ifadesi hangi metotta veya sınıfta? Oluşturan kodun sahiplik sözleşmesini belirle.
Dispose çağrısını kim yapmalı? using, Close() veya açık bir Dispose() çağrısı var mı? Kaynağı oluşturan tarafın mı, kullanan tarafın mı kapatacağını netleştir.
Nesne başka bir metoda devredildi mi? Akış, istemci veya bağlam başka bir metoda parametre olarak gönderiliyor mu? Devralan metodun nesneyi kapatıp kapatmayacağını açıkça belirle.
Asenkron işlem tamamlandı mı? await edilmeden kapsam sona eriyor mu? Kaynak kapanmadan önce ilgili işlemin tamamlanmasını bekle.
Kapsam kullanım bitmeden sona eriyor mu? using bloğundan sonra aynı nesne kullanılmaya devam ediyor mu? Kullanımı doğru kapsamın içine taşı veya yaşam süresini yeniden düzenle.

Aşağıdaki örnekte değişken, using bloğundan sonra hâlâ erişilebilir durumdadır; ancak nesne çoktan dispose edilmiştir:

using System;
using System.IO;

var stream = new MemoryStream();
using (stream)
{
    stream.WriteByte(65);
}

Console.WriteLine(stream.Length);

Bu kodda beklenen çıktı yerine ObjectDisposedException oluşur. Düzeltilmiş sürümde kullanım, kaynağın kapatılacağı kapsam içinde tamamlanır:

using System;
using System.IO;

using var stream = new MemoryStream();
stream.WriteByte(65);

Console.WriteLine(stream.Length);

Beklenen çıktı 1 değeridir. using bildirimi, kapsam sona erdiğinde Dispose() çağrılmasını sağlar. Bu nedenle nesneyi metot sonunda kullanacaksan, o nesneyi daha erken kapatan iç kapsam oluşturmamaya dikkat et. C# using açıklamasında belirtildiği gibi using bloğu sona erdiğinde, normal dönüş veya istisna durumlarında kaynak için kapatma işlemi gerçekleştirilir.

Buradaki temel karar, Dispose() çağrısını kaldırmak değil, sahipliği doğru yere taşımaktır. Bir API nesneyi çağıran kodun kapatmasını bekliyorsa kapatma sorumluluğu sende olabilir. Buna karşılık nesne başka bir bileşen tarafından yönetiliyorsa onu erken kapatmak, o bileşenin sonraki kullanımında aynı hataya neden olabilir.

using, Stream ve dosya işlemlerinde hata nasıl düzeltilir?

using bloğu, kapsam sona erdiğinde IDisposable nesnesinin Dispose edilmesini sağlar. Bu nedenle blok içinde oluşturulan bir Stream nesnesini dışarı döndürüp sonradan kullanmak hataya yol açabilir. C# dil başvurusu, using içindeki kaynakların dönüş olsa bile metot tamamlanmadan önce kapatıldığını açıklar. Microsoft Learn using statement dokümantasyonu bu davranışı try/finally mantığıyla açıklar.

MemoryStream örneği: Sahipliği yanlış belirlemek

Aşağıdaki hatalı metot, oluşturduğu akışı döndürür. Ancak using nedeniyle metot bittiğinde akış çoktan kapatılmıştır:

using System;
using System.IO;

MemoryStream HatalıAkışOlustur()
{
    using var stream = new MemoryStream(new byte[] { 65 });
    return stream;
}

try
{
    var stream = HatalıAkışOlustur();
    Console.WriteLine(stream.ReadByte());
}
catch (ObjectDisposedException ex)
{
    Console.WriteLine(ex.GetType().Name);
}

Beklenen çıktı ObjectDisposedException olur. MemoryStream.ReadByte(), kapatılmış bir akış üzerinde çağrıldığında bu istisnayı oluşturur.

Düzeltilmiş sürümde akışı oluşturan metot kapatma sorumluluğunu üstlenmez. Akışı kullanan kod, işlem tamamlandığında kapatır:

using System;
using System.IO;

MemoryStream AkışOlustur()
{
    return new MemoryStream(new byte[] { 65 });
}

using var stream = AkışOlustur();
Console.WriteLine(stream.ReadByte());

Bu kodun çıktısı 65 olur. Çünkü akış, ReadByte çağrısından sonra ve bulunduğu kapsam sona erdiğinde kapatılır.

Dosya işlemlerinde de aynı kontrol uygulanır. Bir StreamReader ile metni okuyorsan, okunmuş string değerini döndürmek güvenlidir; okuyucunun kendisini döndürmek değildir. Benzer biçimde StreamWriter ile yazma işlemi tamamlanmadan nesneyi kapatmamalı, yazma bittikten sonra kaynakların serbest bırakılmasını sağlamalısın. Çözüm, her durumda Dispose çağrısını kaldırmak değildir. Nesneyi kimin oluşturduğunu, kimin kullandığını ve kimin kapatması gerektiğini birlikte incelemek gerekir. Bu yaklaşım, tüm ObjectDisposedException hatalarına doğrudan genellenemez; bazı nesneler farklı sahiplik ve yaşam döngüsü kurallarıyla çalışır.

HttpClient ve DbContext için sahiplik nasıl belirlenir?

HttpClient ile DbContext ikisi de kapatılabilir nesnelerdir, ancak yaşam döngüleri aynı kalıpla yönetilmez. Önce nesnenin uygulamanın hangi katmanında oluşturulduğunu belirle, ardından isteğin veya veri erişim işleminin tamamlanmasından önce kapatılıp kapatılmadığını kontrol et.

HttpClient: İstek tamamlanmadan kapatma

Aşağıdaki akışta istemci sahibi, istemciyi oluşturan metottur. Bu nedenle Dispose işlemi, asenkron istek tamamlandıktan sonra gerçekleşir:

using var client = new HttpClient();

string içerik = await client.GetStringAsync("https://example.com");
Console.WriteLine(içerik.Length);

Buna karşılık GetStringAsync çağrısından sonra beklemeden client.Dispose() çağırmak hatalıdır. HttpClient.Dispose, bekleyen istekleri iptal edebilir. Bağımlılık enjeksiyonu veya IHttpClientFactory ile sağlanan bir istemciyi alan servis ise nesneyi kendi oluşturmadığı için onu doğrudan kapatmamalıdır..NET belgeleri, uzun ömürlü istemci ile fabrika tarafından üretilen kısa ömürlü istemcilerin farklı yaşam döngüsü modelleri olduğunu belirtir. Microsoft Learn HttpClient yaşam döngüsü rehberi bu modellerin sahiplik farkını açıklar.

DbContext: İş birimi ile kapsamı eşleştirmek

DbContext genellikle tek bir veri erişim iş birimi boyunca kullanılır. Bir web uygulamasında bağımlılık enjeksiyonu ile gelen bağlamın sahibi çoğu zaman istek kapsamıdır. Servis, bağlamı kullanır ancak kendisine verilen örneği using içine alıp kapatmaz:

public async Task KaydetAsync(Order order)
{
    _db.Orders.Add(order);
    await _db.SaveChangesAsync();
}

Bağlam bir fabrikadan veya new ile doğrudan oluşturuluyorsa sahiplik uygulama kodundadır. Bu durumda veri erişimi tamamlanana kadar bağlam açık kalmalı, ardından kapatılmalıdır. Özellikle asenkron sorgu ve SaveChangesAsync çağrıları beklenmeden kapsamdan çıkılmamalıdır. Ayrıca bağlama bağlı bir IQueryable nesnesini kapsam dışına taşımak, sorgu daha sonra çalıştığında kapatılmış bağlama erişilmesine neden olabilir. Doğru çözüm, istemci ve bağlam için tek bir ezber kalıp seçmek değil, oluşturma, devretme, asenkron işlemin tamamlanması ve kapatma sorumluluklarını ayrı ayrı izlemektir.

Stack trace ile üç adımda ObjectDisposedException teşhisi

1. Hata satırını ve nesne adını belirle. Stack trace içinde önce kendi uygulama kodundaki ilk anlamlı satırı bul. Kütüphane satırlarından önce görünen bu satır, kapatılmış nesneye erişimin gerçekleştiği yeri gösterir. Hata mesajında geçen nesne adını da not et: Stream, MemoryStream, DbContext veya başka bir nesne olabilir. ObjectDisposedException, elden çıkarılmış bir nesnenin üyesine yeniden erişildiğinde oluşur.

2. Sahiplik ve kapanma sırasını izle. Nesnenin oluşturulduğu satırı, çevresindeki tüm using bloklarını ve açıkça yazılmış Dispose veya Close çağrılarını ara. Özellikle bir metodun using içinde oluşturduğu nesneyi çağırana döndürüp döndürmediğini kontrol et. using bloğundan yapılan return işlemi, metodun gerçekten dönmesinden önce Dispose çağrılmasını sağlar. Bu nedenle aşağıdaki metotta çağıran tarafa dönen MemoryStream zaten kapatılmıştır.

static MemoryStream Olustur()
{
    using var stream = new MemoryStream(new byte[] { 65 });
    return stream;
}

var stream = Olustur();
Console.WriteLine(stream.ReadByte());

Çözüm, oluşturma ve kullanma sorumluluğunu aynı uygun kapsama taşımaktır:

static MemoryStream Olustur()
{
    return new MemoryStream(new byte[] { 65 });
}

using var stream = Olustur();
Console.WriteLine(stream.ReadByte());

Beklenen çıktı 65 olur. İlk örnekte metot stream'i kapatırken, ikinci örnekte stream'in sahibi çağıran koddur ve okuma tamamlandıktan sonra kapatılır.

3. Async akışını sırayla test et. Nesneyle çalışan asenkron metodun döndürdüğü Task gerçekten await ediliyor mu kontrol et. Await edilmeyen işlem devam ederken metodun kapsamı sona erebilir ve using nesneyi kapatabilir. Bu durumda işlem tamamlanmadan stream, istemci veya veri erişim nesnesi elden çıkarılmış olur. Microsoft dokümantasyonu da await edilmemiş çağrılarda mevcut metodun işlem tamamlanmadan ilerleyebileceğini belirtir.

Düzeltmeden sonra aynı senaryoyu yeniden çalıştır. Breakpoint ile nesnenin oluşturulduğu, kullanıldığı ve kapatıldığı satırları sırayla izle. Gerekirse nesne adını ve metot giriş çıkışlarını kayda al. Ardışık işlem tamamlanmadan Dispose çağrılmadığını doğrulamak için ücretsiz C# bilgi testi ile temel C# kavramlarını da pekiştirebilirsin.

Sık Sorulan Sorular

Dispose çağrısını kaldırmak ObjectDisposedException hatasını her zaman çözer mi?

Hayır. Sorun çoğu zaman Dispose çağrısının varlığı değil, yanlış nesnenin yanlış kapsamda kapatılmasıdır. Oluşturma, kullanım ve kapatma sorumluluğu birlikte incelenmelidir.

using bloğundan dönen Stream neden daha sonra kullanılamaz?

using kapsamı sona erdiğinde stream için otomatik olarak Dispose çağrılır. Nesne değişkeni hâlâ erişilebilir görünse de stream artık kullanım ömrünü tamamlamıştır.

HttpClient ve DbContext için Dispose sorumlusu nasıl belirlenir?

Nesneyi hangi katman oluşturuyorsa sahiplik genellikle o katmandadır. Bir nesne dışarıdan sağlanıyorsa onu kullanan metodun kendiliğinden kapatmaması, sözleşmedeki sahiplik kuralını izlemesi gerekir.

ObjectDisposedException hatası async kodda nasıl teşhis edilir?

İşlemi başlatan çağrının await edilip edilmediğini, kapsamın ne zaman sona erdiğini ve Dispose çağrısının işlem tamamlanmadan yapılıp yapılmadığını adım adım kontrol et.

ObjectDisposedException çözümünde amaç kapatma işlemlerini rastgele kaldırmak değil, nesnenin yaşam süresini gerçek kullanım süresiyle uyumlu hâle getirmektir.

Bu içerik aradığın cevabı verdi mi?
Yanıtın, hangi yazıları geliştirmemiz gerektiğini anlamamıza yardımcı olur.
Bu içeriğin üretilmesinde yapay zeka araçlarından destek alınmıştır.

Bu konudan sonra ne okuyabilirsin?

Tüm yazılar

İlgili Eğitimler

Berk Keskin, yazılım geliştirici ve eğitmen
Yazar

Berk Keskin Kimdir?

Yazılıma 12 yaşında başladı; İzmir Ekonomi Üniversitesi'ni bölüm birincisi ve yüksek şeref öğrencisi olarak tamamladı. Bugün yalnızca eğitim vermekle kalmıyor, sektörde aktif olarak yazılım projeleri geliştiriyor ve gerçek dünya deneyimini birebir derslerine taşıyor. Ezberden uzak, mühendislik zihniyetini merkeze alan sürdürülebilir öğrenme sistemleri tasarlayarak sorgulayan, üreten ve problem çözebilen yeni nesil yazılımcılar yetiştiriyor.

Sektörel Deneyim & Projeler

  • Ticarify Entegrasyon Yazılım logosu CEO Ticarify Entegrasyon YazılımPazaryerleri ve e-ticaret sitelerine otomatik e-fatura kesimi, sipariş ve kargo takibi hizmetleri sunan e-Dönüşüm platformunun API mimarisini ve yazılım ekibini yönetmektedir.
  • Benim Düğünüm logosu CEO Benim DüğünümDijital etkinlik ve anı paylaşım platformu.
  • Siberdizayn logosu Yazılım Ekibi Lideri SiberdizaynYüksek anlık oyuncu trafiğine sahip oyun kontrol panelleri ve sunucu altyapıları geliştiren yazılım ekibine liderlik etmektedir.
  • MEDYOGRAFYA 360° Dijital Çözümler logosu Dijital Strateji Lideri MEDYOGRAFYA 360° Dijital ÇözümlerŞirketlerin dijital çözümlerde uzun vadede nasıl ilerlemesi gerektiği ve dijital dönüşüm süreçlerinin yönetilmesine destek olmaktadır.
  • İzmir Ekonomi Üniversitesi logosu Danışma Kurulu Üyesi İzmir Ekonomi ÜniversitesiMezun olduğu üniversitesinde, Bilgisayar Programcılığı bölümünün akademik müfredatını güncel sektör ihtiyaçlarına göre şekillendirmek adına Danışma Kurulu'nda görev almaktadır.
WhatsApp Hemen Ara