Почему удаленный объект не генерирует исключение при его использовании после удаления?

Законно ли вызывать метод для удаленного объекта? Если да, то почему?

В следующей демонстрационной программе у меня есть одноразовый класс A (который реализует интерфейс IDisposable). Насколько я знаю, если я передаю одноразовый объект в конструкцию using(), то метод Dispose() автоматически вызывается в закрывающей скобке:

A a = new A();
using (a)
{
   //...
}//<--------- a.Dispose() gets called here!

//here the object is supposed to be disposed, 
//and shouldn't be used, as far as I understand.

Если это так, объясните, пожалуйста, вывод этой программы:

public class A : IDisposable
{
   int i = 100;
   public void Dispose()
   {
      Console.WriteLine("Dispose() called");
   }
   public void f()
   {
      Console.WriteLine("{0}", i); i  *= 2;
   }
}

public class Test
{
        public static void Main()
        {
                A a = new A();
                Console.WriteLine("Before using()");
                a.f();
                using ( a) 
                {
                    Console.WriteLine("Inside using()");
                    a.f();
                }
                Console.WriteLine("After using()");
                a.f();
        }
}

Результат (ideone):

Before using()
100
Inside using()
200
Dispose() called
After using()
400

Как я могу вызвать f() для удаленного объекта a? Это разрешено? Если да, то почему? Если нет, то почему приведенная выше программа не выдает исключение во время выполнения?


Я знаю, что популярная конструкция использования using такова:

using (A a = new A())
{
   //working with a
}

Но я просто экспериментирую, поэтому написал по-другому.


person Nawaz    schedule 17.09.2011    source источник
comment
Я вижу, что кому-то не хватает детерминированного характера управления памятью в C++. :)   -  person ChaosPandion    schedule 17.09.2011
comment
Итак, вы говорите: я написал программу, которая не реализует контракт одноразовых объектов, и когда я ее запускаю, она не реализует контракт одноразовых объектов. Вы несете ответственность за реализацию такого поведения. Вы этого не сделали. Иди сделай это.   -  person Eric Lippert    schedule 18.09.2011


Ответы (6)


Утилизирован не значит ушел. Disposed означает только то, что любой неуправляемый ресурс (например, файл, соединение любого типа и т. д.) был освобожден. Хотя обычно это означает, что объект не предоставляет никакой полезной функциональности, все же могут существовать методы, которые не зависят от этого неуправляемого ресурса и продолжают работать как обычно.

Механизм удаления существует, поскольку .net (и, как следствие, C#.net) представляет собой среду со сборкой мусора, что означает, что вы не несете ответственности за управление памятью. Однако сборщик мусора не может решить, закончилось ли использование неуправляемого ресурса, поэтому вам нужно сделать это самостоятельно.

Если вы хотите, чтобы методы выдавали исключение после того, как объект был удален, вам понадобится логическое значение, чтобы зафиксировать статус удаления, и как только объект будет удален, вы выдаете исключение:

public class A : IDisposable
{
   int i = 100;
   bool disposed = false;
   public void Dispose()
   {
      disposed = true;
      Console.WriteLine("Dispose() called");
   }
   public void f()
   {
      if(disposed)
        throw new ObjectDisposedException();

      Console.WriteLine("{0}", i); i  *= 2;
   }
}
person Femaref    schedule 17.09.2011
comment
Удаление должно означать, что любой неуправляемый ресурс освобождается. Но это не обязательно, если класс плохо реализован. - person svick; 18.09.2011
comment
Очевидно. Но так обстоит дело с любым критическим поведением, я предполагал идеальный случай. - person Femaref; 18.09.2011
comment
Но в соответствии с шаблоном Dispose dispose означает, что как управляемые, так и неуправляемые ресурсы высвобождаются при внешнем вызове от клиента. В этом случае я ожидаю, что ни один метод не сработает. Правильно? - person OldSchool; 23.10.2017

Исключение не выбрасывается, потому что вы не разработали методы для выдачи ObjectDisposedException после вызова Dispose.

clr автоматически не знает, что он должен выдать ObjectDisposedException после вызова Dispose. Вы несете ответственность за создание исключения, если Dispose освободил какие-либо ресурсы, необходимые для успешного выполнения ваших методов.

person Giorgi    schedule 17.09.2011

Типичная реализация Dispose() вызывает Dispose() только для любых объектов, которые она хранит в своих полях, которые являются одноразовыми. Что, в свою очередь, освобождает неуправляемые ресурсы. Если вы реализуете IDisposable и на самом деле ничего не делаете, как в своем фрагменте кода, то состояние объекта вообще не меняется. Ничто не может пойти не так. Не путайте удаление с финализацией.

person Hans Passant    schedule 17.09.2011

Цель IDisposable — позволить объекту фиксировать состояние любых внешних сущностей, которые в его интересах были переведены в состояние, далеко не идеальное для других целей. Например, объект Io.Ports.SerialPort мог бы изменить состояние последовательного порта с «доступен для любого приложения, которому он нужен» на «используется только одним конкретным объектом Io.Ports.SerialPort»; основная цель SerialPort.Dispose - восстановить состояние последовательного порта до «доступного для любого приложения».

Конечно, как только объект, реализующий IDisposable, сбрасывает сущности, которые поддерживали определенное состояние в своих интересах, он больше не будет иметь преимуществ поддерживаемого состояния этих сущностей. Например, после того как состояние последовательного порта установлено на «доступен для любого приложения», потоки данных, с которыми он был связан, больше не могут использоваться для отправки и получения данных. Если бы объект мог нормально функционировать без перевода внешних сущностей в специальное состояние для его пользы, не было бы причин оставлять внешние сущности в особом состоянии в первую очередь.

Как правило, после вызова IDisposable.Dispose для объекта не следует ожидать, что объект будет способен выполнять какие-либо действия. Попытка использовать большинство методов для такого объекта указывала бы на ошибку; если нельзя разумно ожидать, что метод будет работать, правильный способ указать это через ObjectDisposedException.

Microsoft предлагает, чтобы почти все методы объекта, реализующего IDisposable, вызывали исключение ObjectDisposedException, если они используются для удаленного объекта. Я бы предположил, что такие советы слишком общие. Для устройств часто очень полезно предоставлять методы или свойства, чтобы узнать, что произошло, пока объект был жив. Хотя можно было бы дать коммуникационному классу метод Close, а также метод Dispose и разрешить только одному запрашивать такие вещи, как NumberOfPacketsExchanged, после закрытия, но не после Dispose, но это кажется чрезмерно сложным. Чтение свойств, связанных с вещами, которые произошли до того, как объект был удален, кажется вполне разумным шаблоном.

person supercat    schedule 18.09.2011

Disposer в C# — это не то же самое, что деструктор в C++. Средство удаления используется для освобождения управляемых (или неуправляемых) ресурсов, в то время как объект остается действительным.

Исключения выбрасываются в зависимости от реализации класса. Если f() не требует использования ваших уже удаленных объектов, то не обязательно создавать исключение.

person Marlon    schedule 17.09.2011

Вызов Dispose() не устанавливает ссылку на объект в null, и ваш пользовательский одноразовый класс не содержит никакой логики для создания исключения, если доступ к его функциям осуществляется после вызова Dispose(), поэтому это, конечно, законно.

В реальном мире Dispose() освобождает неуправляемые ресурсы, и после этого эти ресурсы будут недоступны, и/или автор класса выдает ObjectDisposedException, если вы пытаетесь использовать объект после вызова Dispose(). Обычно логическое значение на уровне класса устанавливается в значение true в теле Dispose(), и это значение проверяется в других членах класса до того, как они выполнят какую-либо работу, с выдачей исключения, если логическое значение равно true.

person Stephen Kennedy    schedule 17.09.2011