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, когда он больше не существует.

Примеры, которые я видел в Интернете, предлагают использовать блок для определения времени жизни объекта и автоматического его удаления системой, но база не распознается при перемещении в тело конструктора, поэтому я не могу написать использование блок вокруг него... что я не уверен, что хотел бы делать в любом случае, так как не будет ли это указывать среде выполнения освобождать объект, который все еще может быть использован позже внутри базового класса?

Тогда мой вопрос, код в порядке? Или как его можно было бы реорганизовать, чтобы устранить предупреждение? Я не хочу подавлять предупреждение, если это действительно не уместно.


person Timothy    schedule 22.04.2010    source источник


Ответы (2)


Если вы используете Visual Studio 2010, CA2000 полностью не работает. Он также может быть нарушен в других версиях FxCop (он же Code Analysis), но VS2010 — единственная, за которую я могу поручиться. Наша кодовая база выдает предупреждения CA2000 для кода, подобного этому...

internal static class ConnectionManager 
{
    public static SqlConnection CreateConnection()
    {
         return new SqlConnection("our connection string");
    }
}

...указывая, что соединение не удаляется до того, как оно выйдет из области действия метода. Что ж, да, это правда, но это не выходит за рамки для приложения, поскольку оно возвращается вызывающей стороне — в этом весь смысл метода! Точно так же ваш аргумент конструктора не выходит за пределы области видимости, а передается базовому классу, поэтому это скорее ложное срабатывание правила, чем реальная проблема.

Раньше это было полезным правилом, но теперь все, что вы действительно можете сделать, это отключить его, пока оно не будет исправлено. Что прискорбно, потому что (очень немногие) фактические положительные моменты должны быть исправлены.

person Greg Beech    schedule 22.04.2010
comment
+1 за «полностью сломан в VS10». Очень трудно убедить людей присоединиться ко мне в поездке по улучшению качества кода, когда инструменты выставляют меня таким глупым... - person Will Dean; 03.11.2010

Не существует безопасного и элегантного способа передать связанному конструктору новый объект 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 будет удален, даже если возникнет исключение между временем его создания и моментом, когда инкапсулирующий объект подвергается воздействию клиентского кода. Шаблон уродлив, но я не уверен, что какой-либо более красивый шаблон обеспечит правильность.

person supercat    schedule 19.02.2014
comment
Разочаровывает то, что вызовы родительского/самостоятельного конструктора синтаксически ограничены без уважительной причины - я бы хотел, чтобы С# позволял вызову base() или this() находиться внутри тела конструктора, что устраняло бы целые классы проблем (и безопасность может быть гарантирована запретив разыменование this перед вызовом base()/this()). - person Dai; 20.07.2021
comment
Я думаю, что цель проекта состояла в том, чтобы предотвратить манипулирование кодом базового объекта, который еще не был создан. ИМХО, это должно было быть достигнуто с помощью синтаксического требования, чтобы не было пути кода, который ссылается на this или успешно возвращается без вызова базового конструктора, а также пути кода, который вызывает базовый конструктор более одного раза. Однако для этой цели синтаксис излишне ограничителен. Я также думаю, что должен был быть отдельный синтаксис для обеспечения ранней и поздней инициализации полей класса, поскольку обе конструкции имеют законное использование. - person supercat; 20.07.2021
comment
и все же, несмотря на это, С# действительно допускает вызовы virtual из конструктора суперкласса, что полностью противоречит сути:/ - person Dai; 20.07.2021
comment
@Dai: я не думаю, что он предоставляет какой-либо механизм для выполнения таких вызовов до того, как управление достигнет конструктора базового класса, не так ли? Если в контракте базового класса указано, что подклассы должны допускать вызов определенных виртуальных методов или свойств до запуска их конструктора (например, свойство, указывающее, какие функции базового класса потребуются производному классу), тот факт, что такие свойства вызываются до того, как производный класс конструктор класса вряд ли должен удивлять. Однако автор базового класса не мог прочитать документацию еще не написанного производного класса. - person supercat; 20.07.2021
comment
Правильно, виртуальные вызовы в ctors выполняются в теле, а не раньше, но проблема в том, что фактическое переопределение виртуального метода, которое вызывается, может получить доступ к состоянию производного класса до его создания - это официально не рекомендуется везде в том числе на C#. Вы упомянули, что это зависит от «контракта», но С# недостаточно выразителен, чтобы компилятор проверял его, и никто не читает документацию :) - person Dai; 20.07.2021
comment
@Dai: Тот, кто наследует класс с виртуальными методами, должен прочитать родительскую документацию о том, что делают методы, и об обстоятельствах, в которых они могут быть вызваны. Наследование от классов без чтения их документации не является рецептом надежных программ. - person supercat; 20.07.2021