Comments 42
Скоро С++ и С# встретятся и влюблятся :) Очень жду дитя, которое они породят :)
Лучше явно очистить ресурсы как можно скорее (вызвав Close, Dispose или оператор using), вместо того чтобы ждать неявной очистки, которая произойдёт «когда-нибудь» (когда сама среда запустит финализаторы).
А точнее, которая может произойти никогда. MSDN намекает на то, что Dispose нужен мягко говоря для другого — не просто что-то закрыть, а освободить неуправляемые ресурсы, которые иначе освобождены никогда не будут.
У автора правильно написано. Освобождение ресурса может быть написано в финализаторе без всякого Dispose. Паттерн нужен в первую очередь для детерминированной очистки ресурсов.
Пардон, "детерминированного освобождения ресурсов"
public struct something : IDisposable
то в каком именно финализаторе он освободится, в несуществующем?
Ещё раз про статью в msdn: там описано исходное назначение паттерна — детерминированное освобождение ресурса. О том что stack-only struct поддерживает только явную очистку захваченных ресурсов у автора также указано в статье, но в msdn не об этом. А для приведённого вами примера характерно использование Dispose для работы с управляемыми ресурсами через Dispose (например, локальный захват локера на запись и его высвобождение в Dispose). В том же случае, если вы ссылаетесь на неуправляемый ресурс в структуре (обычной как в примере, не ссылочной), то это как минимум может привести к нарушению семантики владения ресурсом при копировании структуры (неявном опять таки). Да, согласен, в данном случае финализатора нет и есть хорошая возможность выстрелить в ногу из-за отсутствия финализатора и Dispose тут как бы решает проблему, но, тем ни менее, это не отменяет исходное назначение паттерна.
вместо того чтобы ждать неявной очистки, которая произойдёт «когда-нибудь»
И о моей поправке:
А точнее, которая может произойти никогда
Если не вызывать Dispose явно (или через using), то очистка может не произойти никогда — если это struct (кстати, любая struct). И вы получите утечку памяти/хэндла/еще каких гадостей. Мне кажется, вы совершенно проигнорировали мою исходную позицию, и вместо этого теперь пытаетесь сказать мне то же самое.
И дело тут, кмк, не в
Лучше явно очистить ресурсы как можно скорее
А в том, что если Dispose не вызвать — вы потенциально простреливаете себе ногу.
Еще раз о том, что написано у автора
Тут согласен — формулировка неверная, но лишь отчасти (если ресурс используемый в этой ref структуре не обернут в IDisposable объект
Мне кажется, вы совершенно проигнорировали мою исходную позицию, и вместо этого теперь пытаетесь сказать мне то же самое
Я отвечал именно на эту вот фразу:
MSDN намекает на то, что Dispose нужен мягко говоря для другого — не просто что-то закрыть, а освободить неуправляемые ресурсы, которые иначе освобождены никогда не будут
C этим тезисом я не совсем согласен — в msdn о другом, т.к. если посмотреть на пример в статье, то по нему видно, что даже не вызвав Dispose мы всё равно с течением времени освободим ресурс, но недетерминировано (разумеется, при корректной реализации паттерна, приведенной в качестве примера). В msdn-статье нет ни слова о структурах, т.к. для обычных паттерн юзается не по назначению (и, вообще, зачем нужны явные umanaged в структуре(?), явный антипаттерн и источник проблем за редкими исключениями), а ref появились уже позже и тут вызов Dispose обязателен.
Вобщем, я думаю, вопрос исчерпан.
формулировка неверная, но лишь отчасти
С каких пор у boolean появилось, помимо истины/лжи, третье значение «может быть»?
в msdn о другом
Читаем по ссылке:
Provides a mechanism for releasing unmanaged resources.Перевод: Предоставляет механизм для отчистки неуправляемых ресурсов. Нету ни слова о детерменированности, вы себе ее тут придумываете.
что даже не вызвав Dispose мы всё равно с течением времени освободим ресурс, но недетерминировано
Не освободим, если это struct. Я это уже говорил, но вы решили об этом забыть.
и, вообще, зачем нужны явные umanaged в структуре(?), явный антипаттерн и источник проблем за редкими исключениями
Антипаттерн — это использовать публичный протокол, который требует явной очистки ресурсов, и потом эти ресурсы не очищать. Также антипаттерн — это надеяться на детали реализации, что кто-то за вами там что-то почистит когда-нибудь. А еще антипаттерн — это использовать классы и гонять по ссылке то, что может спокойно себе полежать на стэке:
public struct SomeCThing : IDisposable
{
[DllImport("c.dll")]
private static extern IntPtr create_c_thing(...);
private IntPtr handle;
// Дальше идут обвязочные методы
}
Читаем по ссылке:
Угу, читаем прям вот следующий абзац
Use the Dispose method of this interface to explicitly release unmanaged resources in conjunction with the garbage collector
Не освободим, если это struct. Я это уже говорил, но вы решили об этом забыть.
Попробуйте прочитать весь абзац из которого вырвана эта фраза и понять к чему она относится. Не вижу смысла в очередной раз повторяться.
Антипаттерн — это использовать публичный протокол, который требует явной очистки ресурсов, и потом эти ресурсы не очищать. Также антипаттерн — это надеяться на детали реализации, что кто-то за вами там что-то почистит когда-нибудь.
А я разве где-то писал о том, что явно диспозить объект не нужно? Я говорил об исходном назначении паттерна, описанного в статье msdn.
Касательно структур с unmanaged: да, иногда очень надо для совсем критичных мест и писаться этот участок должен очень аккуратно, т.к. поломать стейт unmanaged ресурса очень легко, если он не весь идемпотентный и требуется хранить даже минимально мутабельный стейт внутри объекта, владеющего unamanaged ресурсом. Для общего случая работы с Unmanaged это скорее антипаттерн.
After — кто-то его порефакторил. Визуально — вполне легальный рефакторинг без претензий компилятора.
Т.е. использование unmanaged в структуре накладывает еще и жесткие ограничения на работу с ней через ref (о которых компилятор не парится), а не только на необходимость вызвать Dispose.
ref struct эту проблему тоже не решает, но хотя бы не дает навесить интерфейс и вызывать методы через боксинг (что тоже кораптит стейт), что уже лучше.
public unsafe struct CustomStream : IDisposable
{
private int _size;
private int _currentPos;
private IntPtr _ptr;
private bool _isDisposed;
public CustomStream(int initialSize)
{
_currentPos = 0;
_size = initialSize;
_ptr = Marshal.AllocHGlobal(initialSize);
_isDisposed = false;
}
public void Append(byte data)
{
if (_currentPos < _size)
{
Marshal.WriteByte(_ptr, _currentPos, data);
// тут вот мы изменяем наш стейт
_currentPos++;
}
}
public void Dispose()
{
if (!_isDisposed)
{
Marshal.FreeHGlobal(_ptr);
_isDisposed = true;
}
}
}
public class Example
{
public void Before()
{
using (CustomStream stream = new CustomStream(1024))
{
stream.Append(0);
stream.Append(1);
// всё ОК
}
}
public void After()
{
using (CustomStream stream = new CustomStream(1024))
{
WriteHeaderBlock(stream);
WriteClosingBlock(stream); // вот тут уже кораптим стейт
}
}
public void WriteHeaderBlock(CustomStream stream)
{
stream.Append(0);
}
public void WriteClosingBlock(CustomStream stream)
{
stream.Append(1);
}
}
В реальности метод Dispose может использоваться для чего угодно. При желании, им можно полностью заменить любые повторяющиеся блоки finally, что стало ещё актуальнее с появлением краткой формы оператора using.
book book = default(book);
try
{
}
finally
{
((IDisposable)book).Dispose(); //тут сюрприз, если book обычная IDisposable структура
}
Я же не говорю что он тоже самое делает с ref. Если компилятор работает лучше с ref структурой, то это только хорошо.
Вариант с юзингом:
IL_000d: ldloca.s b
IL_000f: constrained. C/book
IL_0015: callvirt instance void [System.Runtime]System.IDisposable::Dispose()
Вариант с ручным try-finally:
IL_000e: ldloc.0
IL_000f: box C/book
IL_0014: callvirt instance void [System.Runtime]System.IDisposable::Dispose()
запускал и получал те же результаты на netcore2.2, netcore3.0
тогда у меня следующие вопросы:
1) зачем компилятор это делает (копирует переменную)? Это не то, что я ожидаю
2) почему в этом случае Dispose(), который в finally, вызывается через constrained? Ведь просто вызов (не override) метода у структуры — это просто call. Если структура приводится к интерфейсу — тогда будет box, которого я не вижу в finally
В общем: какую магию применяет компилятор по отношению к структурам в using?
Это важно при явной реализации метода, и не играет никакой роли при публичной реализации.
Это важно при явной реализации метода, и не играет никакой роли при публичной реализации.
вот этого не понял… Что за публичная реализация? Можно пример, в котором видна разница
Dispose
и IDisposable::Dispose
вызовов?upd: вы кстати пишите что если структура(не ref), то упаковки не будет. Хочу уточнить, если я обычную структуру (:IDisposable) использую вместе с using, то упаковки не будет?
Не будет. Сделали специальную оптимизацию в обход спецификации: https://stackoverflow.com/questions/2412981/if-my-struct-implements-idisposable-will-it-be-boxed-when-used-in-a-using-statem/2413844#2413844
Примечание. Не забывайте, что в случае ссылочных структур используется только явная очистка, поскольку определение финализаторов для них невозможно.
Сказано так, будто у обычных структур есть финализатор
С одной стороны — это (ref структуры) требуется в том случае когда нужна небольшая обертка вокруг неуправляемых ресурсов и эта тонкая обертка позволяет детерминированно освобождать их НО с другой стороны очень нужно что бы компилятор как минимум предупреждал что для такой структуры не был вызван метод Dispose и/или эта структура требует использования using оператора или же студия каким либо иным способом (какой — либо анализатор кода ) давала возможность разрабочтику понять что у него потенциальная утечка ресурсов.
Disposable ref structs в C# 8.0