views:

164

answers:

4

The following code generates two CA2000 warnings (among others, but that's not the point).

public sealed class Item: IDisposable
{
    public void Dispose() {}
}

public sealed class ItemContainer
{
    public void Add(Item item)
    {
    }
}

public sealed class Test: IDisposable
{
    private ICollection<Item> itemCollection;
    private ItemContainer itemContainer;

    private void Add(Item item)
    {
        itemCollection.Add(item);
    }

    public void Initialize()
    {
        var item1 = new Item(); // no warning
        itemCollection.Add(item1);

        var item2 = new Item(); // CA2000: call Dispose on object item2
        Add(item2);

        var item3 = new Item(); // CA2000: call Dispose on object item3
        itemContainer.Add(item3);
    }

    public void Dispose() {}
}

Note that there is no warning generated for item1. It seems, Code Analysis assumes the ICollection will take responsibility of the item and eventually dispose it.

Is there a way to mark my Add methods, so that the warning goes away?

I'm looking for something similar to ValidatedNotNullAttribute for CA1062.

Edit: to make it clear: this is not my real code. In the real code, everything is properly disposed.

It's just that CA does not recognize that the call to my Add methods transfers ownership. I would like it to treat my Add methods in the same way it treats ICollection.Add.

Disposing in the same scope is not an option.

+3  A: 

Do you want to fix the code or just suppress the warnings? Suppressing the warnings is straightforward:

[SuppressMessage("Microsoft.Reliability",
                 "CA2000:DisposeObjectsBeforeLosingScope",
                 Justification = "Your reasons go here")]
public void Initialize()
{
    // ...
}
LukeH
I'd prefer to see that with a justification and so would stylecop.
annakata
@annakata: And so would I. Edited to include it...
LukeH
I'd prefer to not suppress the warning. If the method is later modified, I'd like to get a warning, if appropriate.
Henrik
@Henrik: The CA2000 analysis rules seem to be fairly crude and over-zealous. As far as I can tell the only way to keep it happy is to actually dispose of your IDisposables in the same scope (with a few odd exceptions such as the behaviour of `item1` in your example).
LukeH
A lot of the code-analysis rules are crude and over-zealous. One or two are just dumb. I don't think one can consider them on a par with compiler warnings, but as a very different class of tool.
Jon Hanna
A: 

Well of course the first thing to do is to actually have the Dispose method clean up the members of the collection. I'm assuming that's just an error in the example rather than the real code.

Beyond that, I would just suppress the warning. I very strongly hold that any suppression:

  1. Should be of a very short scope, so it doesn't suppress another case of the warning that is a genuine mistake.
  2. Should be annotated with a comment, no matter how brain-dead obvious it seems that the warning is safe to suppress.

That said, I think the analysis of CA2000 is so poor as to not be worth having it checked by default, but only in occasional reviews. After a certain number of false-positives a warning can't even be considered a warning any more, just noise that's hiding real warnings and hence making code more likely to be buggy.

Jon Hanna
+1  A: 

I know this is sample code, and so whether this workaround would work in your real code, I couldn't say.

In this particular case, if you move the object creation code into it's own method, that returns the new Item, then the warning will disappear, e.g. change:

public void Initialize()
 {
  var item1 = new Item(); // no warning
  itemCollection.Add(item1);

  var item2 = CreateItem(); // CA2000 no longer appears
  Add(item2);

  var item3 = new Item(); // CA2000: call Dispose on object item3
  itemContainer.Add(item3);
 }

 private Item CreateItem()
 {
  return new Item();
 }

Obviously, the CreateItem method could be passed arbitrary parameters to pass to the Item constructor.

Edit

Having seen Henrik's answer, and the response on Connect, all I can say is bletch. There's no guarantee that an ICollection implementation also implements IDisposable, and whilst his posted example does implement IDisposable, apparently that's not required to shut up the code analysis (I'd have been somewhat okay if you had to implement both). A class implementing ICollection but not implementing IDisposable is highly unlikely to deal with disposing of contained objects correctly.

Damien_The_Unbeliever
But in this case, the warning will not appear even if Add(item2) is removed and item2 is verifiably not disposed. So I might as well just suppress the warning.
Henrik
@Henrik - the more time I spend looking at this, the more confused I get. There's no guarantee that any particular ICollection<T> implementation will correctly dispose of items added to it, so I keep wondering why we're *not* getting CA2000 for item1.
Damien_The_Unbeliever
Awarded the bounty to this answer. This could be useful in the case where the CreateItem method internally added the item to a collection to make sure it is disposed.
Henrik
A: 

I also asked this at connect.microsoft.com and this is what they answered:

You can workaround the issue by having the container/collection object that adds the disposable object implement ICollection or ICollection<T>. The method that performs the Add must also have name starting with "Add".

And sure enough: when class Test implements ICollection<Item>, then the warning goes away. This is an acceptable solution for the case in question. But it's still an open question what to do, when it's not appropriate to implement ICollection to indicate transfer of ownership.

public sealed class Test: IDisposable, ICollection<Item>
{
    public void Initialize()
    {
        var item1 = new Item(); // no warning
        itemCollection.Add(item1);

        var item2 = new Item(); // no warning
        ((ICollection<Item>)this).Add(item2);

        var item3 = new Item(); // no warning
        AddSomething(item3);
    }

    //... implement ICollection and Method AddSomething
}
Henrik
Accepted my own answer, because this is what I did in this case: implement ICollection on a class that already implemented IDisposable. I totally agree with Damien: it would make more sense, if CA required both IDisposable and ICollection.
Henrik