CA2000 передает объектную ссылку на базовый конструктор в C#
Я получаю предупреждение, когда запускаю какой-то код с помощью утилиты анализа кода Visual Studio, и я не уверен, как ее решить. Возможно, кто-то здесь сталкивался с подобной проблемой, решил ее и хочет поделиться своим пониманием.
Я программирую специально нарисованную ячейку, используемую в элементе управления DataGridView. Код напоминает:
public class DataGridViewMyCustomColumn : DataGridViewColumn
{
public DataGridViewMyCustomColumn() : base(new DataGridViewMyCustomCell())
{
}
Он генерирует следующее предупреждение:
CA2000: Microsoft.Reliability: в методе 'DataGridViewMyCustomColumn.DataGridViewMyCustomColumn()' вызывайте System.IDisposable.Dispose для объекта 'new DataGridViewMyCustomCell()' до того, как все ссылки на него выйдут из области видимости.
Я понимаю, что это предупреждает меня, что DataGridViewMyCustomCell (или класс, от которого он наследуется) реализует интерфейс IDisposable, и метод Dispose() должен вызываться для очистки любых ресурсов, заявленных DataGridViewMyCustomCell, когда его больше нет.
Примеры, которые я видел в интернете, предлагают использовать блок для определения времени жизни объекта и автоматического его удаления системой, но база не распознается при перемещении в тело конструктора, поэтому я не могу написать использование вокруг него... что я в любом случае не хотел бы делать, так как это не указывало бы во время выполнения освободить объект, который еще можно было бы использовать позже в базовом классе?
Мой вопрос тогда, код в порядке, как есть? Или как это можно изменить, чтобы устранить предупреждение? Я не хочу подавлять предупреждение, если это действительно уместно.
2 ответа
Если вы используете Visual Studio 2010, то CA2000 полностью сломан. Это может также быть сломано в других версиях FxCop (иначе Code Analysis), но VS2010 - единственная, за которую я могу поручиться. Наша кодовая база дает CA2000 предупреждения для кода, подобного этому...
internal static class ConnectionManager
{
public static SqlConnection CreateConnection()
{
return new SqlConnection("our connection string");
}
}
... указывает на то, что соединение не удаляется до того, как оно выйдет за рамки метода. Ну, да, это правда, но это не выходит за рамки приложения, так как оно возвращается вызывающей стороне - в этом весь смысл метода! Таким же образом, ваш аргумент конструктора не выходит за пределы области видимости, а передается в базовый класс, так что это ложное срабатывание правила, а не реальная проблема.
Раньше это было полезным правилом, но теперь все, что вы можете сделать, это выключить его, пока они не исправят это. К сожалению, потому что (очень мало) фактических положительных моментов - это то, что должно быть исправлено.
Не существует безопасного и элегантного способа, чтобы конструктор с цепями передавал новый IDisposable
объект базового конструктора, поскольку, как вы заметили, невозможно заключить вызов связанного конструктора в любой вид try finally
блок. Есть подход, который безопасен, но вряд ли элегантен: определите метод утилиты примерно так:
internal static TV storeAndReturn<TR,TV>(ref TR dest, TV value) where TV:TR
{
dest = value; return value;
}
Пусть конструктор будет выглядеть примерно так:
protected DataGridViewMyCustomColumn(ref IDisposable cleaner) :
base(storeAndReturn(ref cleaner, new DataGridViewMyCustomCell()))
{
}
Код, которому нужен новый объект, должен затем вызывать открытый статический метод фабрики, который будет вызывать соответствующий конструктор внутри try
/finally
блок, основная линия которого будет обнуляться cleaner
незадолго до того, как это закончилось, и чей finally
блок будет звонить Dispose
на cleaner
если это не ноль. При условии, что каждый подкласс определяет аналогичный метод фабрики, этот подход гарантирует, что новый IDisposable
объект будет удален, даже если между временем его создания и временем, в течение которого инкапсулируемый объект будет открыт клиентскому коду, возникнет исключение. Шаблон ужасен, но я не уверен, что какой-либо более хороший шаблон обеспечит корректность.