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

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

В следующей демонстрационной программе у меня есть одноразовый класс 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
}

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

Ответ 1

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

Механизм Disposeing существует как .net(и наследуется, С#.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;
   }
}

Ответ 2

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

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

Ответ 3

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

Ответ 4

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

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

Ответ 5

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

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

Ответ 6

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

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

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

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