Giter Club home page Giter Club logo

janitor's Introduction

Janitor.Fody

Chat on Gitter NuGet Status

Simplifies the implementation of IDisposable.

See Milestones for release notes.

This is an add-in for Fody

It is expected that all developers using Fody become a Patron on OpenCollective. See Licensing/Patron FAQ for more information.

Usage

See also Fody usage.

NuGet installation

Install the Janitor.Fody NuGet package and update the Fody NuGet package:

PM> Install-Package Fody
PM> Install-Package Janitor.Fody

The Install-Package Fody is required since NuGet always defaults to the oldest, and most buggy, version of any dependency.

Add to FodyWeavers.xml

Add <Janitor/> to FodyWeavers.xml

<Weavers>
  <Janitor/>
</Weavers>

What it does

  • Looks for all classes with a Dispose method.
  • Finds all instance fields that are IDisposable and cleans them up.
  • Adds an int disposeSignaled field that is Interlocked.Exchangeed inside Dispose.
  • Uses disposeSignaled to add an exit clause to Dispose.
  • Uses disposeSignaled to add a guard clause to all non-private instance methods. This will cause an ObjectDisposedException to be thrown if the class has been disposed.
  • Supports convention based overrides for custom disposing of managed and unmanaged resources.
  • Adds a finalizer when clean-up of unmanaged resources is required
  • Uses the Dispose(isDisposing) convention when clean-up of unmanaged resources is required

Simple Case

All instance fields will be cleaned up in the Dispose method.

The Code

public class Sample : IDisposable
{
    MemoryStream stream;

    public Sample()
    {
        stream = new MemoryStream();
    }

    public void Method()
    {
        //Some code
    }

    public void Dispose()
    {
        //must be empty
    }
}

What gets compiled

public class Sample : IDisposable
{
    MemoryStream stream;
    int disposeSignaled;
    bool disposed;

    public Sample()
    {
        stream = new MemoryStream();
    }

    public void Method()
    {
        ThrowIfDisposed();
        //Some code
    }

    void ThrowIfDisposed()
    {
        if (disposed)
        {
            throw new ObjectDisposedException("TemplateClass");
        }
    }

    public void Dispose()
    {
        if (Interlocked.Exchange(ref disposeSignaled, 1) != 0)
        {
            return;
        }
        var temp = Interlocked.Exchange<IDisposable>(ref stream, null);
        if (temp != null)
        {
            temp.Dispose();
        }
        disposed = true;
    }
}

Custom managed handling

In some cases you may want to have custom code that cleans up your managed resources. If this is the case add a method void DisposeManaged()

Your Code

public class Sample : IDisposable
{
    MemoryStream stream;

    public Sample()
    {
        stream = new MemoryStream();
    }

    public void Method()
    {
        //Some code
    }

    public void Dispose()
    {
        //must be empty
    }

    void DisposeManaged()
    {
        if (stream != null)
        {
            stream.Dispose();
            stream = null;
        }
    }
}

What gets compiled

public class Sample : IDisposable
{
    MemoryStream stream;
    int disposeSignaled;
    bool disposed;

    public Sample()
    {
        stream = new MemoryStream();
    }

    void DisposeManaged()
    {
        if (stream != null)
        {
            stream.Dispose();
            stream = null;
        }
    }

    public void Method()
    {
        ThrowIfDisposed();
        //Some code
    }

    void ThrowIfDisposed()
    {
        if (disposed)
        {
            throw new ObjectDisposedException("TemplateClass");
        }
    }

    public void Dispose()
    {
        if (Interlocked.Exchange(ref disposeSignaled, 1) != 0)
        {
            return;
        }
        DisposeManaged();
        disposed = true;
    }
}

Custom unmanaged handling

In some cases you may want to have custom code that cleans up your unmanaged resources. If this is the case add a method void DisposeUnmanaged()

The Code

public class Sample : IDisposable
{
    IntPtr handle;

    public Sample()
    {
        handle = new IntPtr();
    }

    public void Method()
    {
        //Some code
    }

    public void Dispose()
    {
        //must be empty
    }

    void DisposeUnmanaged()
    {
        CloseHandle(handle);
        handle = IntPtr.Zero;
    }

    [DllImport("kernel32.dll", SetLastError=true)]
    static extern bool CloseHandle(IntPtr hObject);
}

What gets compiled

public class Sample : IDisposable
{
    IntPtr handle;
    int disposeSignaled;
    bool disposed;

    public Sample()
    {
        handle = new IntPtr();
    }

    void DisposeUnmanaged()
    {
        CloseHandle(handle);
        handle = IntPtr.Zero;
    }

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern Boolean CloseHandle(IntPtr handle);

    public void Method()
    {
        ThrowIfDisposed();
        //Some code
    }

    void ThrowIfDisposed()
    {
        if (disposed)
        {
            throw new ObjectDisposedException("TemplateClass");
        }
    }

    public void Dispose()
    {
        if (Interlocked.Exchange(ref disposeSignaled, 1) != 0)
        {
            return;
        }
        DisposeUnmanaged();
        GC.SuppressFinalize(this);
        disposed = true;
    }

    ~Sample()
    {
        Dispose();
    }
}

Custom managed and unmanaged handling

Combining the above two scenarios will give you the following

The code

public class Sample : IDisposable
{
    MemoryStream stream;
    IntPtr handle;

    public Sample()
    {
        stream = new MemoryStream();
        handle = new IntPtr();
    }

    void DisposeUnmanaged()
    {
        CloseHandle(handle);
        handle = IntPtr.Zero;
    }

    void DisposeManaged()
    {
        if (stream != null)
        {
            stream.Dispose();
            stream = null;
        }
    }

    [DllImport("kernel32.dll", SetLastError=true)]
    static extern bool CloseHandle(IntPtr hObject);

    public void Method()
    {
        //Some code
    }

    public void Dispose()
    {
        //must be empty
    }
}

What gets compiled

public class Sample : IDisposable
{
    MemoryStream stream;
    IntPtr handle;
    int disposeSignaled;

    public Sample()
    {
        stream = new MemoryStream();
        handle = new IntPtr();
    }

    public void Method()
    {
        ThrowIfDisposed();
        //Some code
    }

    void DisposeUnmanaged()
    {
        CloseHandle(handle);
        handle = IntPtr.Zero;
    }

    void DisposeManaged()
    {
        if (stream != null)
        {
            stream.Dispose();
            stream = null;
        }
    }

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern Boolean CloseHandle(IntPtr handle);

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    void ThrowIfDisposed()
    {
        if (disposeSignaled !=0)
        {
            throw new ObjectDisposedException("Sample");
        }
    }

    public void Dispose(bool disposing)
    {
        if (Interlocked.Exchange(ref disposeSignaled, 1) != 0)
        {
            return;
        }
        if (disposing)
        {
            DisposeManaged();
        }
        DisposeUnmanaged();
    }

    ~Sample()
    {
        Dispose(false);
    }
}

What's with the empty Dispose()

Notice that the Dispose() is empty in all of the above cases. This is because Janitor controls what goes in there. In fact if you put any code in there Janitor will throw an exception. If you want to control IDisposable for specific types use [Janitor.SkipWeaving] attribute applied to the type or [Janitor.SkipWeavingNamespace("namespaceToSkip")] to the assembly. Then Janitor wont touch it.

Why not weave in IDisposable

So it is technically possible to flag a type, with an attribute or a custom interface, and inject the full implementation of IDisposable. This would mean the empty Dispose method would not be required. However, since Fody operates after a compile, it would mean you would not be able to use the type in question as if it was IDisposable when in the same assembly. You would also not be able to use it as IDisposable within the same solution since intellisense has no knowledge of the how Fody manipulates an assembly.

What about base classes

Not currently supported.

Icon

Spray Bottle designed by Julieta Felix from The Noun Project.

janitor's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

janitor's Issues

struct with base classes can not be skipped

Not sure if this is just me or the addin.

I got an (inner) struct with some unsupported base classes. Can't add [Janitor.SkipWeaving] - which in this case would be the answer - as this is restricted to classes and fields. (BTW: I'm aware that this struct usage is anything but best practice. But it's good working third party code so I don't touch it more than I need.)

Code example - removed the implementations as not needed for demonstration:

public class ObservableDictionary<TKey, TValue> :
        IDictionary<TKey, TValue>,
        ICollection<KeyValuePair<TKey, TValue>>,
        IEnumerable<KeyValuePair<TKey, TValue>>,
        IDictionary,
        ICollection,
        IEnumerable,
        ISerializable,
        IDeserializationCallback,
        INotifyCollectionChanged,
        INotifyPropertyChanged
        {
            [Serializable, StructLayout(LayoutKind.Sequential)]
            public struct Enumerator<TKey, TValue> : IEnumerator<KeyValuePair<TKey, TValue>>, IDictionaryEnumerator, IEnumerator
            { 

            }
        }

Possible solutions could be to extend the attributes scope to structs or ignore such kind of structs at all.

Skip weaving of all types in namespace

When using source-only packages, the IDisposable implementations of the "external" types can conflict with Janitor. This usually requires adding [SkipWeaving] attributes to the code.

  • This works when the code is fully copy&pasted or using the "old" nuget source-only style but it requires some effort to go through all affected types. Also it makes the code harder to update, as you have to go though the updated code again an readd all the attributes.
  • This doesn't work when using the "new" nuget source-only package style because changes to the source file are only applied to the file in your local nuget cache and won't be applied the projects on different machines.

sadly, it looks like it's not possible to add attributes to namespaces directly. I have a working spike which uses assembly-level attributes instead, the usage would look like this:
[assembly: SkipWeavingNamespace("NamespaceToSkip")].
I'm happy to open a PR if you're interested in this spike.

Readonly field Dispose

Hi,

I am wondering why Janitor can not dispose private readonly fields (which are IDisposable), even if it possible by myself?

[] Fody/Janitor: Could not add dispose for field '_networkInfo' since it is marked as readonly. Change this field to not be readonly.

Kind regards,
Aleksandr

InvalidOperationException thrown when using class with generic type constraints

Whenever you use Janitor on a class which has a generic type constraint, any use of the methods of the class will throw System.InvalidOperationException: Could not execute the method because either the method itself or the containing type is not fully instantiated.

Here is a small example app which shows the issue.

  class Foo<T> : IDisposable
    where T : IComparable
  {
    public void Dispose() { }
    private void DisposeManaged() { }
    public void Bar() { }
  }

  public class Program
  {
    static void Main(string[] args)
    {
      using (var foo = new Foo<int>())
        foo.Bar();
    }
  }

Why disposed is so optimistic?

disposed = true; should be in start of Dispose method. Not in end.

Imagine situation if somebody uses Method which uses stream while you release steam out of example.

You will not get good error about ObjDisposedEx, but what you will get is random errors.

Please make this dispose be safe, put disposed = true; into start of Dispose method.

Prevent racing condition at dispose time

In a multi-threaded situation the dispose flag should be set at the end of the Dispose method.

Currently the Dispose is injected with the following code:

public void Dispose()
{
   if (Interlocked.Exchange(ref this.disposeSignaled, 1) != 0)
        return;

   //Disposing code
}

The issue is that if you have a class that receives events in different threads, you can't prevent it from receiving an ObjectDisposedException.

Adding Fody Janitor attribute to communicate intend and assure presence of Janitor

We started to make use of Janitor in one of our projects and ran in the situation in which it was not clear why an object implemented an empty Dispose method. This can of course be clarified by adding a comment. However it got me thinking, wouldn't it be better to add an attribute which could (optionally) be placed on Dispose methods. This would serve two purposes, communicate intend, lead to build failure if Janitor is accidentally removed.

The attribute could be named JanitorManaged for example.

class Foo : Disposable
{
  [JanitorManaged]
  public void Dispose()
  {}
}

Please share your thoughts on this.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.