16 Nisan 2018 Pazartesi

IDisposable Örüntüsü

Giriş
Açıklaması şöyle. Sınıfın Disposable olan bir alanı varsa, sınıf ta IDisposable örüntüsünü gerçekleştirmelidir.
Implement IDisposable only if you are using unmanaged resources directly. If your app simply uses an object that implements IDisposable, don't provide an IDisposable implementation. Instead, you should call the object's IDisposable.Dispose implementation when you are finished using it.
Finalizer Garbage Collection'ın bir parçasıdır. Dispose ise GC yani bellek ile ilgili değildir. Bizim kapatmayı istediğimiz managed kaynakları yönetir. Dolayısıyla Dispose kullanmamız memory leak olmadığı anlamına gelmez. Sadece nesne ile işimiz bitince unmaged kaynağın kapatılmasını sağlarız.

1. IDisposable Arayüzü
Sınıfımızı IDisposable arayüzünden kalıtırım.
class Test : IDisposable
{
  ...
}
2. public Dispose metodu
Sadece Dispose() metodunu gerçekleştirmek yetmez.
class Test : IDisposable
{
    public string Property { get; set; } = "Test";
    public void Dispose() => Console.WriteLine("Disposed, very scary");
}
O zaman code analysis yapınca şöyle bir hata alırım.
CA1063 Implement IDisposable correctly Provide an overridable implementation of Dispose(bool) on 'User' or mark the type as sealed. A call to Dispose(false) should only clean up native resources. A call to Dispose(true) should clean up both managed and native resources. stman User.cs 10
Tavsiye edilen şöyle yapılması.
public class User : IDisposable
{
 
  public void Dispose()
  {
    Dispose(true);
    GC.SuppressFinalize(this);
  }

  protected virtual void Dispose(bool disposing)
  {
    if (disposing) 
    {
      // free managed resources
    }
    // free native resources if there are any.
}
GC.SuppressFinalize() çağrısının açıklaması şöyle
Then we "tell" garbage collector to suppress finalizer for this object because we are releasing
its memory and doesnt need to be finalized.
Calling finalizer(destructor) of a given type is expensive and tweaks like this help us improve performance of the application.

3. Tavsiye Edilen protected Dispose Metodu İçin
Tavsiye edilen protected Dispose yöntemini kullanmak istersek önümüzde şu seçenekler var.

Yöntem 1
Şöyle yaparız. Bu yöntemde yönetilen kaynağın null olup olmamasına bakılır
class FantasticFileService : IDisposable
{
  private FileSystemWatcher fileWatch; // FileSystemWatcher is IDisposable

  public FantasticFileService(string watch)
  {
    fileWatch = new FileSystemWatcher(watch);
    fileWatch.Changed += OnFileChanged;
  }

  ~FantasticFileService()
  {
    Dispose(false);
  }

  protected virtual void Dispose(bool disposing)
  {
    if (disposing && fileWatch != null)
    {
      fileWatch.Dispose();
      fileWatch = null;
    }
  }

  public void Dispose()
  {
    Dispose(true);
    GC.SuppressFinalize(this);
  }
}
Yöntem 2
Şöyle yaparız. Bu yöndemte nesnenin _disposed isimli bir alanı bulunur. Bu alanı kullanarak kaynağı yok edip etmediğimizi takip ederiz.
public class SimpleClass : IDisposable
{
  // managed resources SqlConnection implements IDisposable as well.
  private SqlConnection _connection;
  private bool _disposed;

  // implementing IDisposable
  public void Dispose()
  {
    this.Dispose(true);

    GC.SuppressFinalize(this);
  }

  protected virtual void Dispose(bool disposing)
  {
    if (_disposed) { return; }
    if (_connection != null)
    {
      _connection.Dispose();
      _connection = null;
    }
    _disposed = true;

    // call base Dispose(flag) method if we are using hierarchy.
  }
}
4. Nesnemiz using ile kullanılır
Daha sonra nesnemiz using ile kullanılır
Örnek
Şöyle yaparız.
class Disposable : IDisposable
{
    public void Dispose()
    {
    }
}


static void Main(string[] args)
{
  using (var disposable1 = new Disposable())
  {
    Console.WriteLine("using");
  }
}
Şu kodla ayndır
var disposable2 = new Disposable();
try
{
  Console.WriteLine("try");
}
finally
{
  if (disposable2 != null)
    ((IDisposable)disposable2).Dispose();
}
Örnek
Elimizde şöyle bir sınıf olsun. Lock metodu factory gibi çalışıp IDisposable'dan kalıtan_Lock nesnesi döner.
public sealed class AsyncMutex
{
  private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1);

  public async Task<IDisposable> Lock()
  {
    await Semaphore.WaitAsync().ConfigureAwait(false);
    return new _Lock(Semaphore);
  }

  private sealed class _Lock : IDisposable
  {
    private readonly SemaphoreSlim Semaphore;

    public _Lock(SemaphoreSlim semaphore) => Semaphore = semaphore;

    void IDisposable.Dispose() => Semaphore.Release();
  }
}
Kullanmak için şöyle yaparız.
private readonly AsyncMutex Mutex = new AsyncMutex();

public async Task FooAsync()
{
  using (_ = await Mutex.Lock())
  {
    // Do stuff here
  }
}
Diğer
using ve unhandled exception ile uygulamanın sonlanması
Açıklaması şöyle.
Within a handled exception, the associated finally block is guaranteed to be run. However, if the exception is unhandled, execution of the finally block is dependent on how the exception unwind operation is triggered. That, in turn, is dependent on how your computer is set up. For more information, see Unhandled Exception Processing in the CLR.
Usually, when an unhandled exception ends an application, whether or not the finally block is run is not important. However, if you have statements in a finally block that must be run even in that situation, one solution is to add a catch block to the try-finally statement. Alternatively, you can catch the exception that might be thrown in the try block of a try-finally statement higher up the call stack. That is, you can catch the exception in the method that calls the method that contains the try-finally statement, or in the method that calls that method, or in any method in the call stack. If the exception is not caught, execution of the finally block depends on whether the operating system chooses to trigger an exception unwind operation.
Elimizde şöyle bir kod olsun.
namespace dispose_test
{
  class Program
  {
    static void Main(string[] args)
    {
      using (var disp = new MyDisposable())
      {
        throw new Exception("Boom");
      }
    }
  }

  public class MyDisposable : IDisposable
  {
    public void Dispose()
    {
      Console.WriteLine("Disposed");
    }
  }
}
Eğer unhandled exception ile uygulama sonlanırsa, nesnemizin Dispose metodu çağrılmayabilir.

CA2202: Do not dispose objects multiple times (Nesneleri birden çok kez atmayın)
CA2202 bir stream'in birden fazla kez kapatılma ihtimali varsa verilir. Uyarı şöyledir
Warning CA2202 Object '...' can be disposed more than once in method '...'. To avoid generating a System.ObjectDisposedException you should not call Dispose more than one time on an object.




Hiç yorum yok:

Yorum Gönder