Здравствуйте, HowardLovekraft, Вы писали:
HL>Пруф?
Читайте спецификацию... Ну и Липперта...
HL>Что вы подразумеваете под словом "многопоточный" в данном случае?
Вызов методов объекта в разных потоках. Никаких гарантий синхронизации для вызова конструктора не даётся. В этом плане он ничем не отличается от прочих instance-методов.
Re: Стоит ли переносить инициализацию в отдельный метод?
Не надо поясничать. Есть проверка аргументов и примитивная логика инициализации вроде присвоения начальных значений полям. Этому делу самое место в конструкторе.
А есть тяжелый код с выделением ресурсов, так что конструктор выглядит явно переусложненным. Если такой конструктор бросит исключение в using (Foo foo = new Foo()), то Dispose не будет вызван и ресурсы останутся неосвобожденными.
Re[4]: Стоит ли переносить инициализацию в отдельный метод?
Здравствуйте, abibok, Вы писали:
A>>Срочно вынести в отдельный метод!
A>Не надо поясничать. Есть проверка аргументов и примитивная логика инициализации вроде присвоения начальных значений полям. Этому делу самое место в конструкторе. A>А есть тяжелый код с выделением ресурсов, так что конструктор выглядит явно переусложненным. Если такой конструктор бросит исключение в using (Foo foo = new Foo()), то Dispose не будет вызван и ресурсы останутся неосвобожденными.
Ок, допустим сделали мы так:
class Foo : IDisposable
{
public Foo(...)
{
//проверка аргументов и примитивная логика инициализации вроде присвоения начальных значений полям.
}
public void Initialize()
{
//тяжелый код с выделением ресурсов
}
public void Dispose()
{
if(disposed)
return;
//проверяем, какие ресурсы были выделены и освобождаем их
}
}
using (Foo foo = new Foo())
{
//...
foo.Initialize()
//...
}
Метод Initialize может быть и не вызван вовсе. Или в нем в середине было выкинуто исключение и не все ресурсы были выделены. Поэтому в Dispose нужно вставить проверки вида
if(someresource != null)
someresource.Dispose();
Но что мешает нам переписать этот кода как-то так:
class Foo : IDisposable
{
public Foo(...)
{
//проверка аргументов и примитивная логика инициализации вроде присвоения начальных значений полям.try
{
Initialize();
}
catch
{
Dispose();
throw;
}
}
privatevoid Initialize()
{
//тяжелый код с выделением ресурсов
}
public void Dispose()
{
if(disposed)
return;
//проверяем, какие ресурсы были выделены и освобождаем их
}
}
?
Это избавило бы клиентский код от необходимости вызывать Initialize().
Более того, в классе Foo не пришлось бы делать дополнительных приседаний, чтобы корректно работать в ситуации со множественными вызовами Initialize() — метод торчит наружу, никто не запрещает его вызвать дважды.
Re[5]: Стоит ли переносить инициализацию в отдельный метод?
Вы подменяете одну проблему другой. Речь шла о дизайне конструктора, а не о контроле за правильностью соблюдения контракта класса.
A>Или в нем в середине было выкинуто исключение и не все ресурсы были выделены. Поэтому в Dispose нужно вставить проверки вида
Конечно, а разве вы делаете иначе? Знаете чем опасны исключения, брошенные в Dispose в частности и в finally вообще?
Re[6]: Стоит ли переносить инициализацию в отдельный метод?
Здравствуйте, abibok, Вы писали:
A>>Метод Initialize может быть и не вызван вовсе. A>Вы подменяете одну проблему другой. Речь шла о дизайне конструктора, а не о контроле за правильностью соблюдения контракта класса.
Разве?
Есть ли у этого подхода преимущества перед обычной инициализацией через конструктор в C#?
Где тут про дизайн конструктора? Здесь альтернатива конструктор vs отдельный метод инициализации.
Re[7]: Стоит ли переносить инициализацию в отдельный метод?
A>>Вы подменяете одну проблему другой. Речь шла о дизайне конструктора, а не о контроле за правильностью соблюдения контракта класса. A>Разве?
Да.
A>
Есть ли у этого подхода преимущества перед обычной инициализацией через конструктор в C#?
A>Где тут про дизайн конструктора? Здесь альтернатива конструктор vs отдельный метод инициализации.
Дизайн = "Должен ли код инициализации находиться внутри конструктора или его надо выносить в отдельный метод. И в каких случаях каждое из решений имеет преимущества".
А про "Забыли вызвать Initialize" — это ваши домыслы. Есть что сказать по существу вопроса?
Re[4]: Стоит ли переносить инициализацию в отдельный метод?
От:
Аноним
Дата:
03.07.13 03:34
Оценка:
Здравствуйте, abibok, Вы писали:
A>А есть тяжелый код с выделением ресурсов, так что конструктор выглядит явно переусложненным. Если такой конструктор бросит исключение в using (Foo foo = new Foo()), то Dispose не будет вызван и ресурсы останутся неосвобожденными.
Спасибо. Именно этот момент и интересовал.
Просьба пояснить. A>>>Если код инициализации может бросать исключения, то лучше вынести его в отдельный метод. Особенно если класс реализует IDisposable.
Если код инициализации не выделяет ресурсов (но может бросать исключения), то почему лучше тоже вынести его в отдельный метод?
Re[5]: Стоит ли переносить инициализацию в отдельный метод?
А>Просьба пояснить. A>>>>Если код инициализации может бросать исключения, то лучше вынести его в отдельный метод. Особенно если класс реализует IDisposable. А>Если код инициализации не выделяет ресурсов (но может бросать исключения), то почему лучше тоже вынести его в отдельный метод?
Потому что на практике ожидается, что конструктор не бросает исключений, не связанных с проверкой аргументов.
Т.е. сначала создают объект, а потом заворачивают в try-catch вызовы его методов.
Конструктор, бросающий исключения из тяжелой логики внутри — это примерно того же уровня бяка, что property getter, бросающий исключение из тяжелой логики внутри. Никто не запрещает, и в ряде случаев так и надо делать, но по возможности стоит избегать.
А представьте себе, что вы создаете объект некоторого класса не через new, а используя reflection. Исключение из конструктора завернется в какой-нибудь TargetInvocationException и настоящий call stack потеряется. А если хочется перехватить исключение до того, как оно было брошено (чтобы сохранить хороший дамп, например), то вместо простого создания объекта через активатор придется делать вот так:
public static object CreateInstance(Type type, object[] arguments)
{
if (arguments == null || arguments.Length == 0)
{
Expression newExp = Expression.New(type);
Func<object> activator = Expression.Lambda<Func<object>>(Expression.Convert(newExp, typeof(object))).Compile();
return activator();
}
else
{
Type[] argTypes = arguments.Select(arg => arg.GetType()).ToArray();
ConstructorInfo constructor = type.GetConstructor(argTypes);
if (constructor == null)
{
string msg = String.Format(
"Constructor on type {0} that takes in parameters of type [{1}] not found.",
type,
String.Join<Type>(",", argTypes));
throw new MissingMethodException(msg);
}
ParameterExpression param = Expression.Parameter(typeof(object[]), "args");
List<Expression> argsExp = new List<Expression>();
for (int i = 0; i < arguments.Length; i++)
{
Expression paramAccessorExp = Expression.ArrayIndex(param, Expression.Constant(i));
Expression paramCastExp = Expression.Convert(paramAccessorExp, argTypes[i]);
argsExp.Add(paramCastExp);
}
Expression newExp = Expression.New(constructor, argsExp);
Func<object[], object> activator = Expression.Lambda<Func<object[], object>>(newExp, param).Compile();
return activator(arguments);
}
}
Re[6]: Стоит ли переносить инициализацию в отдельный метод?
Здравствуйте, abibok, Вы писали:
A>А представьте себе, что вы создаете объект некоторого класса не через new, а используя reflection. Исключение из конструктора завернется в какой-нибудь TargetInvocationException и настоящий call stack потеряется. А если хочется перехватить исключение до того, как оно было брошено (чтобы сохранить хороший дамп, например), то вместо простого создания объекта через активатор придется делать вот так:
[километр кода скипнут]
Или просто посмотреть содержимое ex.InnerException
Здравствуйте, drol, Вы писали:
D>Читайте спецификацию...
Спецификацию чего (ссылку на документ + номер пункта/параграфа)? D>Ну и Липперта...
Ссылку на пост, в котором это описано, пожалуйста.
Утверждение, что CLR может выполнить вызов финализатора для объекта, у которого в данный момент времени выполняется конструктор, выглядит несколько... хм... нелогичным.
Чтоб CLR позвала финализатор, должна произойти сборка мусора, после которой ссылка на объект будет помещена в F-reachable queue. Соответственно, объект должен быть недостижим. Если в какой-то момент времени выполняется конструктор объекта, это значит, что он очень даже достижим.
D>Вызов методов объекта в разных потоках. Никаких гарантий синхронизации для вызова конструктора не даётся
Приведите пример кода, в котором (хотя бы потенциально) может возникнуть одновременный вызов конструктора у одного и того же объекта.
Как не старался воспроизвести то, о чем говорит drol не вышло
Foo foo = new Foo();
Thread instantiationThread = new Thread(() =>
{
do
{
foo = new Foo();
} while (true);
});
Thread checkInitializedThread = new Thread(() =>
{
do
{
if (!foo._initialized1 | !foo._initialized2)
{
Console.WriteLine("Bingo!!!");
}
} while (true);
});
instantiationThread.Start();
checkInitializedThread.Start();
class Foo
{
public bool _initialized1;
public bool _initialized2;
public Foo()
{
//Thread.Sleep(10);
_initialized1 = true;
Thread.Sleep(10);
_initialized2 = true;
}
}
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания http://rsdn.ru/Info/rules.xml
Здравствуйте, igor-booch, Вы писали:
IB>skipped
"Неинициализированный" (то, о чем пишет TK) и "недостижимый" (то, что делает объект целью GC) — это разные вещи. Ждем пруфлинков.
HL>"Неинициализированный" (то, о чем пишет TK) и "недостижимый" (то, что делает объект целью GC) — это разные вещи. Ждем пруфлинков.
Я имел ввиду что возможность доступа к не инициализированному объекту это ступенька к недостижимости до не инициализированного объекта.
Если доступ к не инициализированному в конструкторе объекту невозможен, то и невозможна потеря ссылки на этот объект до конца его инициализации.
Так как сначала мы получаем доступ (присваиваем переменной ссылку на экземпляр), и только потом мы можем потерять доступ, присвоив переменной ссылку на другой экземпляр (или null).
Отсутствие ссылок на экземпляр это его недостижимость.
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания http://rsdn.ru/Info/rules.xml
HL>"Неинициализированный" (то, о чем пишет TK) и "недостижимый" (то, что делает объект целью GC) — это разные вещи. Ждем пруфлинков.
Написать в интернетах могут все-что угодно. Пример кода это железный proof.
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания http://rsdn.ru/Info/rules.xml
.
Какие еще могут быть проблемы?
А Initialize(...) тоже не может выкидывать исключения не связанные с проверкой аргументов?
Проверка аргументов понятие растяжимое.
Она может быть любой сложности.
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания http://rsdn.ru/Info/rules.xml
Re[6]: Стоит ли переносить инициализацию в отдельный метод?