В следующем коде выделенный ресурс (экземпляр MyClass) не будет освобождён
using System;
class Program
{
static void Main() {
try {
using(var x = new MyClass { Value = 1, }) {
// some work
}//using
} catch {
Console.WriteLine("Catch");
}//try
}
}
class MyClass : IDisposable
{
public MyClass() {
Console.WriteLine("Ctor");
}
public object Value {
get { return null; }
set {
Console.WriteLine("Set property value");
throw new Exception();
}
}
public void Dispose() {
Console.WriteLine("Disposed");
}
}
Будьте бдительны! Жаль, Object Initializer удобная и удачная конструкция, но не везде применима.
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, alexsoff, Вы писали:
_FR>>В следующем коде выделенный ресурс (экземпляр MyClass) не будет освобождён A>Юзинг не будет вызван и в следующем коде:
Должен быть вызван Dispose()
A>class MyClass : IDisposable
A>{
A> public MyClass()
A> {
A> Console.WriteLine("Ctor");
A> throw new Exception(); }
A>А так как инициалайзеры призваны упростить создание конструкторов со множественными параметрами, этого следует ожидать.
Общепринятым соглашением (о правильно написанных классах) является то, что если метод выделения ресурса (в данном случае конструктор) завершился неудачей, то вызвавшему метод освобождать ничего не нужно. В данном случае (исключение в конструкторе) ошибку (не освободил ресурс) допустил автор MyClass. Это он должен обеспечить то, что или конструктор отработает успешно, или (в случае неуспеха) утечки ресурса не будет.
В случае же со свойством ресурс уже выделен корректно.
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, alexsoff, Вы писали:
_FR>>Общепринятым соглашением (о правильно написанных классах) является то, что если метод выделения ресурса (в данном случае конструктор) завершился неудачей, то вызвавшему метод освобождать ничего не нужно. A>Но если прочитать об инициалайзерах: A>здесь
A>Object initializers let you assign values to any accessible fields or properties of an object at creation time without having to explicitly invoke a constructor.
A>То такое поведение вполне логично.
Что здесь значит "такое"?
A>Поля инициализируются в момент создания ресурса.
В том-то и дело, что нет! Object initializers создают видимость того, что "Поля инициализируются в момент создания ресурса", но по факту это различные (не атомарные) операции.
A>Тем самым, если возникло исключение, то _FR>>то вызвавшему метод освобождать ничего не нужно
Вот ведь какое дело: во втором шарпе проблем не возникало:
using(Resource x = new Resource()) {
x.Value = xxx;
}//using
а с появлением нового синтаксиса, его (синтаксиса) использование может привести к проблемам и тут автор класса бессилен.
То есть правильно, по идее должен генериться такой код, что "вызвавшему метод освобождать ничего не нужно", но этого не происходит. Тот программист, что не понимает, как устроен Object initializer, может легко пропустить такую тонкость.
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, TK, Вы писали:
_FR>>Здесь на лицо криво написанный метод выделения ресурса. Люди, заботящиеся о своей карме, делают так: TK>Это костыли, а не забота о карме. Те кто заботится о карме не кидают исключений из сеттеров.
Из геттеров.
Avoid throwing exceptions from property getters.
(Property Design) Да и то "Avoid", а не строгое "Do not". Там же:
Do preserve the previous value if a property setter throws an exception.
что так же косвенно (но явно) говорит о возможности(или, вернее, законности) возникновения исключения в сеттере.
Если оторваться от текущей задачи, то при вызове сеттера можно легко натолкнуться на исключение: например, возбуждается event PropertyChanged и в одном из обработчиков срабатывает исключение. Формально — исключение возникло и в сеттере, так что обеспечить exception free сеттеров очень сложно. Второе: есть FileStream.Position Property
Конечно, это не плохой дизайн, когда все операции, которые могут привести к исключительной ситуации объявлены как методы, а не как свойства. Но многие операции очень привычно "оборачивать" именно свойствами с возможностью throw в сеттере.
Далее. В том моём примере внутри try может быть не только установка свойств, а несколько различных операций, которые при любых "кармических взглядах" имеют право сделать throw, так что "паттерн" приведённый в том моём сообщении не "костыль". Любопытно было бы взглянуть на другую реализацию той же задачи
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, alexsoff, Вы писали:
_FR>>Будьте бдительны! Жаль, Object Initializer удобная и удачная конструкция, но не везде применима. A>Нашел время поэксперементировать. A>сделал следующий финт:
A> using ( var mc = new MyClass().DoSomething() ) {
A>Результат аналогичный! Dispose не вызвался.
В данном случае синтаксис не оставляет сомнений в том, что using должен сработать над результатом .DoSomething(). В моём примере данный вывод не так очевиден.
A>Это меня навело на мыcли, что using просто транслируется в try finnaly.
Это знать, как бы, необходимо — в любой книжке про это написано
A>И получается, что такое поведение вполне закономерно.
С тем, что закономерно — конечно согласен. Внимание же стоит обратить на то, что в некоторых случаях "такое поведение" не вполне очевидно.
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, cadet354, Вы писали:
_FR>>а с появлением нового синтаксиса, его (синтаксиса) использование может привести к проблемам и тут автор класса бессилен. C>это не эквивалентный код, вот эквивалент
Здравствуйте, TK, Вы писали:
TK>Это костыли, а не забота о карме. Те кто заботится о карме не кидают исключений из сеттеров.
+1
Я если хочу кинуть исключение, при изменении проперти, тогда я это оборачиваю в set метод.
<skipped/>
A>Результат аналогичный! Dispose не вызвался. A>
A>Default constructor
A>Inside catch
В спецификации четко описано, во что транслируется using
A using statement of the form
using (ResourceType resource = expression) statement
corresponds to one of two possible expansions. When ResourceType is a value type, the expansion is
{
ResourceType resource = expression;
try {
statement;
}
finally {
((IDisposable)resource).Dispose();
}
}
Otherwise, when ResourceType is a reference type, the expansion is
{
ResourceType resource = expression;
try {
statement;
}
finally {
if (resource != null) ((IDisposable)resource).Dispose();
}
}
видно, что захват ресурса происходит за рамками блока try. Соответственно, если при захвате было выброшено исключение, то до Dispose дело просто не дойдет.
Здравствуйте, _FRED_, Вы писали:
TK>>Предложенный "паттерн" имеет смысл только для использования с "унаследованным" кодом — выглядит он ужасно. _FR>Что именно не нравится?
Не нравится идея с "передачей владения" объектом.
_FR>И чем это будет лучше? И, заодно, можешь примерно показать код метода Resolve<>? В частности, неужели в коде этого метода можно обойтись без единого try? Тогда в чём принципиальная разница с моим примером?
Инициализация объекта может идти в два этапа — на первом этепе создается экземпляр и он добавляется в контейнер. На втором этапе выполняется "инициализация" свойств/конфигурирование объекта. Возникновение исключений на любом этапе — проблема контейнера. Конечный пользователь несет ответственность только за "освобождение" контейнера.
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Здравствуйте, _FRED_, Вы писали:
_FR>Будьте бдительны! Жаль, Object Initializer удобная и удачная конструкция, но не везде применима.
А я только недавно узнал про скрытую локальную переменную при инициализации Object Initializer'ом, подумал — вот как хорошо, если при инициализации исключение будет, то инициализируемая локальная останется null, как если бы в конструкторе исключение было, а оно вон какие эффекты даёт...
Здравствуйте, Пельмешко, Вы писали:
_FR>>Будьте бдительны! Жаль, Object Initializer удобная и удачная конструкция, но не везде применима.
П>А я только недавно узнал про скрытую локальную переменную при инициализации Object Initializer'ом, подумал — вот как хорошо, если при инициализации исключение будет, то инициализируемая локальная останется null, как если бы в конструкторе исключение было, а оно вон какие эффекты даёт...
Между прочим, в этих граблях виноват… ты Я их увидел, прочитав этот пост
Здравствуйте, _FRED_, Вы писали:
_FR>В следующем коде выделенный ресурс (экземпляр MyClass) не будет освобождён
Юзинг не будет вызван и в следующем коде:
class Program
{
static void Main()
{
try
{
using (var x = new MyClass())
{
}//using
}
catch(Exception ex)
{
Console.WriteLine("Catch");
}//try
Console.ReadKey();
}
}
class MyClass : IDisposable
{
public MyClass()
{
Console.WriteLine("Ctor");
throw new Exception(); }
public object Value
{
get { return null; }
set
{
Console.WriteLine("Set property value");
throw new Exception();
}
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <filterpriority>2</filterpriority>public void Dispose()
{
Console.WriteLine("Disposed");
}
}
А так как инициалайзеры призваны упростить создание конструкторов со множественными параметрами, этого следует ожидать.
Здравствуйте, _FRED_, Вы писали:
_FR>Здравствуйте, Пельмешко, Вы писали:
_FR>>>Будьте бдительны! Жаль, Object Initializer удобная и удачная конструкция, но не везде применима.
П>>А я только недавно узнал про скрытую локальную переменную при инициализации Object Initializer'ом, подумал — вот как хорошо, если при инициализации исключение будет, то инициализируемая локальная останется null, как если бы в конструкторе исключение было, а оно вон какие эффекты даёт...
_FR>Между прочим, в этих граблях виноват… ты Я их увидел, прочитав этот пост
type MyClass =
new() = { } then printfn "Ctor"
member x.Value
with get() = null :> obj
and set(_) = printfn "Set property value"
failwith "uups!"
interface System.IDisposable with
member x.Dispose() = printfn "Disposed"let main() =
try use x = new MyClass(Value = box 1)
() (* some work *)with _ -> printfn "Catch"
> main();;
Ctor
Set property value
Catch
Интересно, а как будет выглядеть правильно сгенерированный код для using в данной ситуации?
Здравствуйте, _FRED_, Вы писали:
_FR>Должен быть вызван Dispose()
Да, я это имел ввиду
_FR>Общепринятым соглашением (о правильно написанных классах) является то, что если метод выделения ресурса (в данном случае конструктор) завершился неудачей, то вызвавшему метод освобождать ничего не нужно.
Но если прочитать об инициалайзерах: здесь
Object initializers let you assign values to any accessible fields or properties of an object at creation time without having to explicitly invoke a constructor.
То такое поведение вполне логично. Поля инициализируются в момент создания ресурса. Тем самым, если возникло исключение, то _FR>то вызвавшему метод освобождать ничего не нужно
Здравствуйте, alexsoff, Вы писали:
A>То такое поведение вполне логично. Поля инициализируются в момент создания ресурса. Тем самым, если возникло исключение, то _FR>>то вызвавшему метод освобождать ничего не нужно
А кто должен освобождать? Сеттер свойства? А откуда он узнает, что он вызван из object initializer? Я думаю, что дизайн языка здесь не совсем продуман.
Здравствуйте, nikov, Вы писали:
A>>То такое поведение вполне логично. Поля инициализируются в момент создания ресурса. Тем самым, если возникло исключение, то _FR>>>то вызвавшему метод освобождать ничего не нужно
N>А кто должен освобождать? Сеттер свойства? А откуда он узнает, что он вызван из object initializer?
Конечно нет.
N>Я думаю, что дизайн языка здесь не совсем продуман.
Именно, но, ИМХО, не столько языка (того, что мы пишем), сколько реализации Object Initializer (того, что происходит в компиляторе).
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, nikov, Вы писали:
N>А кто должен освобождать? Сеттер свойства? А откуда он узнает, что он вызван из object initializer? Я думаю, что дизайн языка здесь не совсем продуман.
Так, стоп. Всмысле кто должен освобождать? сказано же в контексте инишиалайзера, думается что ресурсы еще не выделялись, это все равно, что вы создали бы параметры в конструкторе и внутри бы инициализировали свойста.
Откуда свойства знают, что они инициализируются в конструкторе?
Здравствуйте, _FRED_, Вы писали:
_FR>Здравствуйте, alexsoff, Вы писали:
_FR>>>В следующем коде выделенный ресурс (экземпляр MyClass) не будет освобождён A>>Юзинг не будет вызван и в следующем коде:
_FR>Должен быть вызван Dispose()
точно вызовется? мои тесты показали вывод
Ctor
Catch
да и не должен вызываться это же эквивалент такого:
var x = new MyClass();
try{
x.Value=1;
}
finally{
x.Dispose();
}
Здравствуйте, cadet354, Вы писали:
_FR>>>>В следующем коде выделенный ресурс (экземпляр MyClass) не будет освобождён A>>>Юзинг не будет вызван и в следующем коде: _FR>>Должен быть вызван Dispose() C>точно вызовется? мои тесты показали вывод
Тесты чего? Покажи, что тестишь :о)) Оригинальный код или пример alexsoff?
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, _FRED_, Вы писали:
_FR>В том-то и дело, что нет! Object initializers создают видимость того, но по факту это различные (не атомарные) операции.
Ну здрасти, я же Вам и ссылку в msdn и процитировал. Что msdn вводит в заблуждение?
Object initializers let you assign values to any accessible fields or properties of an object at creation time without having to explicitly invoke a constructor
_FR>а с появлением нового синтаксиса
Нужно хорошенько ознакомиться с ньюансами
Здравствуйте, _FRED_, Вы писали:
_FR>а с появлением нового синтаксиса, его (синтаксиса) использование может привести к проблемам и тут автор класса бессилен.
это не эквивалентный код, вот эквивалент
Здравствуйте, cadet354, Вы писали:
_FR>>Тесты чего? Покажи, что тестишь :о)) Оригинальный код или пример alexsoff?
C> using (var x = new MyClass())
C> {
C> x.Value = 1;
C> // some work
C> } //using
И что в этом примере смущает? при возникновении исключения в точке выделения ресурса (в конструкторе) вызывать Dispose и не нужно, потому что считается, что ресурс не выделился я же ровно об этом в ответ и писал. Если что-то не ясно оказалось — спроси пожалуйста конкретнее.
Help will always be given at Hogwarts to those who ask for it.
A>MyClass cl = new MyClass();
A> try
A> {
A> cl.Value = "111";
A> }finally {
A> cl.Dispose();
A> }
Так тоже правильно, но задача другая. Из функции CreateNewResource() нужно вернуть готовый к использованию ресурс, поэтому Dispose() там вызывается только в случае ошибки. А тут если ниже дописать:
A>MyClass cl = new MyClass();
A>try
A>{
A> cl.Value = "111";
A>} finally {
A> cl.Dispose();
A>}
return cl; // Бессмысленная [скорее всего] операция, потому что cl уже уничтожен.
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, _FRED_, Вы писали: A>>} _FR> return cl; // Бессмысленная [скорее всего] операция, потому что cl уже уничтожен.
Согласен бессмыслино, но мы же тут вроде разбираем аналоги using
Здравствуйте, alexsoff, Вы писали:
N>>Потому что finally выполняется даже если не было исключения. A>А что мы должны ресурсы освобождать, когда только возникает исключение?
В данном методе — да, так как это метод CreateNewResource для создания ресурса. Если ресурс успешно создан, то о его закрытии должен позаботиться вызывающий метод после того, как он завершит работу с ним.
Здравствуйте, _FRED_, Вы писали:
_FR>Здравствуйте, cadet354, Вы писали:
_FR>>>а с появлением нового синтаксиса, его (синтаксиса) использование может привести к проблемам и тут автор класса бессилен. C>>это не эквивалентный код, вот эквивалент _FR>
C>>и совершенно логично что диспоза не будет
_FR>Здесь на лицо криво написанный метод выделения ресурса. Люди, заботящиеся о своей карме, делают так:
я про это и не спорю,
мой пример показал во что разворачивается использование object initialization в using,
и следовательно не понятно твое удивление (что не будет вызван dispose)
по этому поводу.
Здравствуйте, cadet354, Вы писали:
C>смущает твой ответ: C>[q] A>>Юзинг не будет вызван и в следующем коде:
C>Должен быть вызван Dispose()
Мой ответ про "Должен быть вызван Dispose()" никакого отношения к коду ниже не имеет, потому что лишь уточняет фразу "Юзинг не будет вызван и в следующем коде:".
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, _FRED_, Вы писали:
_FR>Далее. В том моём примере внутри try может быть не только установка свойств, а несколько различных операций, которые при любых "кармических взглядах" имеют право сделать throw, так что "паттерн" приведённый в том моём сообщении не "костыль". Любопытно было бы взглянуть на другую реализацию той же задачи
Предложенный "паттерн" имеет смысл только для использования с "унаследованным" кодом — выглядит он ужасно. Другая реализация задачи может быть такой:
using (var container = new Container())
{
var resource = container.Resolve<Resource>(parameters)
resource(...);
}
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Здравствуйте, TK, Вы писали:
_FR>>Далее. В том моём примере внутри try может быть не только установка свойств, а несколько различных операций, которые при любых "кармических взглядах" имеют право сделать throw, так что "паттерн" приведённый в том моём сообщении не "костыль". Любопытно было бы взглянуть на другую реализацию той же задачи
TK>Предложенный "паттерн" имеет смысл только для использования с "унаследованным" кодом — выглядит он ужасно.
Что именно не нравится?
TK>Другая реализация задачи может быть такой:
TK>using (var container = new Container())
TK>{
TK> var resource = container.Resolve<Resource>(parameters)
TK> resource(...);
TK>}
И чем это будет лучше? И, заодно, можешь примерно показать код метода Resolve<>? В частности, неужели в коде этого метода можно обойтись без единого try? Тогда в чём принципиальная разница с моим примером?
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, _FRED_, Вы писали: _FR>Будьте бдительны! Жаль, Object Initializer удобная и удачная конструкция, но не везде применима.
Нашел время поэксперементировать.
сделал следующий финт:
class Program
{
static void Main(string[] args)
{
try {
using ( var mc = new MyClass().DoSomething() ) {
Console.WriteLine( "Inside using" );
}
} catch ( Exception ex ) {
Console.WriteLine( "Inside catch" );
} //catch
Console.ReadKey();
}
}
public class MyClass :IDisposable
{
/// <summary>
/// Initializes a new instance of the <see cref="T:System.Object" /> class.
/// </summary>public MyClass()
{
Console.WriteLine( "Default constructor" );
filed1 = "Default value";
}
/// <summary>
/// Initializes a new instance of the <see cref="T:System.Object" /> class.
/// </summary>public MyClass(string val)
{
filed1 = val;
}
public MyClass DoSomething()
{
throw new Exception();
Console.WriteLine( "DoSomething" );
return this;
}
#region Implementation of IDisposable
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <filterpriority>2</filterpriority>public void Dispose()
{
Console.WriteLine( "Disposed: field = " + filed1 );
}
#endregion
private string filed1 = string.Empty;
public string Filed1
{
get { return filed1; }
set {
filed1 = value;
throw new Exception();
}
}
}
Результат аналогичный! Dispose не вызвался.
Default constructor
Inside catch
Это меня навело на мыcли, что using просто транслируется в try finnaly.
И получается, что такое поведение вполне закономерно.
Или у Вас другое мнение?