Avoiding code analysis CA2000 warning with chained constructors?
The normal pattern for avoiding the CA2000 warning about non-disposed locals is to use a temp variable that gets disposed if anything goes wrong, like:
Foo f = null;
try
{
f = new Foo();
Foo result = f;
f = null;
return result;
}
finally
{
if (f != null)
{
f.Dispose();
}
}
A bit verbose, but it works, and it makes sense. But how does one apply that pattern to chained constructors, like this:
public HomeController ( IDataRepository db )
{
this.repo = db ?? new SqlDataRepository();
}
public HomeController ( )
: this(new SqlDataRepository())
{
}
This code is throwing up two CA2000 warnings, one per constructor. The first one I can get rid of using the temp variable pattern. It's pretty annoying, since there doesn't see to be any way for the local to go out of scope after it's constructed but before it gets assigned to the member field, which gets cleaned up later. So I dunno what CA's problem is, but at least I know what to do to fix it.
But, as far as I can figure out, there isn't any alternative way to write the second constructor call to introduce a try/finally. The field being assigned to is read-only, so it must be set in a constructor. And C# won't let you call chained constructors anywhere but immediately before the constructor body. And, of the two, the second warning is actually the more legitimate one -- the call to the chained constructor could (in theo开发者_如何学Cry, if it did any actual work) throw an exception and leave the newly constructed parameter undisposed.
Of course, I can always just suppress this message (which CA2000 seems to need a lot of), but if there's an actual way to eliminate the problem I'd prefer to do that.
I cannot repro the CA2000 violation on the constructor that takes a IDataRepository argument. Given this, along with the fact that you use the same "default" for both constructors, the simplest change that would avoid CA2000 problems for the sample scenario is:
public HomeController(IDataRepository db)
{
this.repo = db ?? new SqlDataRepository();
}
public HomeController()
: this(null)
{
}
Obviously, this wouldn't work too well if your first constructor did not accept a null parameter value. If this were the case and you're absolutely married to the idea of setting the corresponding field in only one place, you would still have options for avoiding CA2000, such invoking a slightly smarter private constructor. e.g.:
public HomeController(IDataRepository db)
: this(() => db, false)
{
if (db == null)
{
throw new ArgumentNullException("db");
}
}
public HomeController()
: this(() => new SqlDataRepository(), true)
{
}
private HomeController(Func<IDataRepository> repositoryRetriever, bool disposeOnFailure)
{
IDataRepository repository = repositoryRetriever.Invoke();
try
{
this.repo = repository;
}
catch
{
if (disposeOnFailure)
{
repository.Dispose();
}
throw;
}
}
Personally, I think the above is pretty nasty hack, particularly given that it involves increasing both code complexity and the chances of a runtime exception in the interests of avoiding a potential problem that wasn't very serious in the first place. My recommendation would be to simply ignore potential CA2000 violations of this sort unless both of the following are true:
- There is a real chance of a non-crashing exception between the instantiation of the object and the end of the method in which it is assigned to a field.
- The consequences of failing to dispose the orphaned instance are fairly serious (e.g.: leaving a file locked).
精彩评论