Здравствуйте, drol, Вы писали: D>Это верно только для однопоточной ситуации. В многопоточном случае возможны такие "чудеса", как выполнение конструктора параллельно с финализатором.
Чтобы финализатор начал выполняться параллельно с конструктором, нужно, чтобы поток исполнения конструктора дошёл до места, после которого нет обращений к this.
Это означает, что если у нас есть недоинициализированный экземпляр, то ссылка на него удерживается в рутах GC, и в лапы финализатора он не попадёт.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, drol, Вы писали: D>>Это верно только для однопоточной ситуации. В многопоточном случае возможны такие "чудеса", как выполнение конструктора параллельно с финализатором. S>Чтобы финализатор начал выполняться параллельно с конструктором, нужно, чтобы поток исполнения конструктора дошёл до места, после которого нет обращений к this. S>Это означает, что если у нас есть недоинициализированный экземпляр, то ссылка на него удерживается в рутах GC, и в лапы финализатора он не попадёт.
Понятно. То есть все будет работать так, как будто финализатор работает после конструктора до тех пор, пока у финализатора не будет какого-нибудь хитрого побочного эффекта, влияющего на наблюдаемое поведение приложения (а не только освобождение занятых системных ресурсов).
Удалось такого добиться на основе глобального изменяемого состояния:
public class A
{
public static int N;
private int x;
public A()
{
N = 42;
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine(N);
//x = N; //uncomment me
}
~A()
{
N = 666;
}
}
static void Main()
{
new A();
Console.WriteLine("Done...");
Console.ReadKey();
return;
}
В релизе при запуске вне студии выводит 666. Если раскоментировать "x = N;" в последней строке конструктора, то выводит 42.
Здравствуйте, Аноним, Вы писали:
А>Есть ли у этого подхода преимущества перед обычной инициализацией через конструктор в C#?
It depends. Например, если инициализация включает в себя логику, специфичную для отдельных сценариев, вместо двух конструкторов удобней использовать static-методы вида CreateForA(), CreateForB().
Если речь идёт про выносе всего кода из конструктора в отдельный метод — практически не встречал. В большинстве случаев удобней разбить логику на отдельные методы и превратить инициализацию в вызовы этих методов из конструктора.
Re[2]: Стоит ли переносить инициализацию в отдельный метод?
Здравствуйте, Nikolay_Ch, Вы писали: N_C>Ну... к примеру, если активно используются интерфейсы, то единственный нормальный способ проинициализировать экземпляр — это метод.
А можете раскрыть мысль? Совершенно не улавливается связь между инициализацией и интерфейсами.
ИМХО существование объекта в невалидном состоянии между вызовами публичных методов — плохая практика. Соответственно и публичные методы инициализации, которые позволяют существовать объекту в невалидном состоянии в течении времени между созданием объекта и вызовом метода инициализации, есть нехорошо. Единственное оправдание всяких Form.OnCreate Control.Initialize это низкая сложность входа, отсутствие необходимости объяснять что такое Dependency Injection и возможность скрыть сложность фабрики форм.
А>Есть ли у этого подхода преимущества перед обычной инициализацией через конструктор в C#?
Есть недостаток, нужно во всех методах проверять проинициализирован экземпляр или нет, и если нет выкидывать эксепшен.
А при инициализации через конструктор, у Вас либо есть проинициализированный экземпляр, либо его нет.
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания http://rsdn.ru/Info/rules.xml
Re[3]: Стоит ли переносить инициализацию в отдельный метод?
Здравствуйте, Tom, Вы писали:
Tom>Это до того момента когда вам тесты понадобится писать. В целом для нормально тестируемого кода надо забыть что такое статический метод, или статическая переменная
Или не нагибать код под тесты. Можно просто использовать Fakes/Mole, можно сочетать тесты с ассертами, можно вообще отказаться от юнит-тестов, обвешать код контрактами и ловить оставшуюся мелочь сразу на интеграционных/функциональных тестах. Я видел все три варианта, не сказал бы что любой из них был сильно лучше/хуже прочих.
Здравствуйте, Sinix, Вы писали:
S>Здравствуйте, bl-blx, Вы писали:
BB>>Нет, я просто не соображу, где там барьер, который гарантирует видимость другим потоком.
S>Под x86/x64 по-прежнему работает правило "all writes are volatile". Под арм/итаниум — с оговорками (см C# Memory Model Implementation on ARM).
Да, спасибо — забыл об этом, но, тем не менее, второй поток делает non-volatile read — с чего бы ему в main memory лезть?
Задача — сохранить максимум информации о контексте приложения в момент возникновения исключительной ситуации для логирования и последующей отладки. Это включает в себя правильный callstack со всей информацией, включая значения локальных переменных и параметров функций в каждом фрейме, а не только текстовое представление стека вызовов. Для этого исключение нужно перехватывать сразу после того, как оно было брошено и до того, как оно войдет в блок catch.
S>Если как написано в другом посте, проблема в отладке — в настройках (debug-exceptions) можно включить прерывание отладки в момент броска исключения.
Речь идет не только об отладке, но и о штатной работе программы. Плюс такая настройка будет прерывать отладку для любых исключений, а нас интересуют только те, которые unhandled.
Здравствуйте, Sinix, Вы писали:
S>Ок, а чем ещё можно объяснить, что пример igor-booch (чуть допилил) не падает?
Тем, что JIT у CLR очень недалёкий. Видно же в ассемблере, что чтение ссылки из памяти происходит на каждой итерации. Замените потроха checkInitializedThread на более простую конструкцию:
var local = foo;
do
{
} while (local==foo);
Console.WriteLine("local != foo");
...и всё сразу станет по-другому (при запуске Release-сборки вне Visual Studio).
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: Стоит ли переносить инициализацию в отдельный метод?
A>>Вы подменяете одну проблему другой. Речь шла о дизайне конструктора, а не о контроле за правильностью соблюдения контракта класса. A>Разве?
Да.
A>
Есть ли у этого подхода преимущества перед обычной инициализацией через конструктор в C#?
A>Где тут про дизайн конструктора? Здесь альтернатива конструктор vs отдельный метод инициализации.
Дизайн = "Должен ли код инициализации находиться внутри конструктора или его надо выносить в отдельный метод. И в каких случаях каждое из решений имеет преимущества".
А про "Забыли вызвать Initialize" — это ваши домыслы. Есть что сказать по существу вопроса?
Здравствуйте, 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
HL>"Неинициализированный" (то, о чем пишет TK) и "недостижимый" (то, что делает объект целью GC) — это разные вещи. Ждем пруфлинков.
Написать в интернетах могут все-что угодно. Пример кода это железный proof.
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания http://rsdn.ru/Info/rules.xml
Здравствуйте, bl-blx, Вы писали:
BB>Да, спасибо — забыл об этом, но, тем не менее, второй поток делает non-volatile read — с чего бы ему в main memory лезть?
Это нужно спрашивать у специалистов, если верить stackoverflow — под x86/64 обновление кэша берёт на себя сам процессор.
Здравствуйте, drol, Вы писали:
D>Ты как-то узко понимаешь инициализацию.
Вот есть поле. Конструктор проставил в него ссылку на объект. А теперь вызывает какие-то методы этого объекта. С этого момента зависимости по this уже нет. Однако инициализация идёт во всю.
Инициализация — это, по определению, изменение состояния объекта. Как только мы перестали менять состояние объекта — его инициализация завершилась.
Если финализатор объекта рассчитывает на какое-то определённое состояние другого объекта — это вина финализатора.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Стоит ли переносить инициализацию в отдельный метод?
От:
Аноним
Дата:
02.07.13 03:23
Оценка:
Есть ли у этого подхода преимущества перед обычной инициализацией через конструктор в C#?
Re[2]: Стоит ли переносить инициализацию в отдельный метод?
От:
Аноним
Дата:
02.07.13 05:31
Оценка:
Здравствуйте, Sinix, Вы писали:
S>It depends. Например, если инициализация включает в себя логику, специфичную для отдельных сценариев, вместо двух конструкторов удобней использовать static-методы вида CreateForA(), CreateForB().
S>Если речь идёт про выносе всего кода из конструктора в отдельный метод — практически не встречал. В большинстве случаев удобней разбить логику на отдельные методы и превратить инициализацию в вызовы этих методов из конструктора.
Всё так. Спасибо.
Re: Стоит ли переносить инициализацию в отдельный метод?
Здравствуйте, Аноним, Вы писали:
А>Есть ли у этого подхода преимущества перед обычной инициализацией через конструктор в C#?
Ну... к примеру, если активно используются интерфейсы, то единственный нормальный способ проинициализировать экземпляр — это метод.
Re[3]: Стоит ли переносить инициализацию в отдельный метод?
Здравствуйте, Аноним, Вы писали:
А>А можете раскрыть мысль? Совершенно не улавливается связь между инициализацией и интерфейсами.
Если у Вас есть интерфейс, который реализуется совершенно разными объектами (не наследуемых друг от друга). Есть объекты, которые получают на вход экземпляр объекта и должны его инициализировать. Вам как-то надо придумать единый метод инициализации этих объектов, т.к. конструктор здесь не подойдет.
А>ИМХО существование объекта в невалидном состоянии между вызовами публичных методов — плохая практика.
ИМХО практика нормальная. К примеру как при работе с БД — у объекта Connection есть конструктор, а есть метод Open. Таких ситуаций множество можно описать.
Re[4]: Стоит ли переносить инициализацию в отдельный метод?
От:
Аноним
Дата:
02.07.13 07:02
Оценка:
Здравствуйте, Nikolay_Ch, Вы писали:
N_C>Здравствуйте, Аноним, Вы писали:
А>>А можете раскрыть мысль? Совершенно не улавливается связь между инициализацией и интерфейсами. N_C>Если у Вас есть интерфейс, который реализуется совершенно разными объектами (не наследуемых друг от друга). Есть объекты, которые получают на вход экземпляр объекта и должны его инициализировать. Вам как-то надо придумать единый метод инициализации этих объектов, т.к. конструктор здесь не подойдет.
Как конструктор может не подойти для инициализации объекта?
Я правильно понял вашу мысль: кто-то(кто?) создал объекты и теперь просит их проинициализировать уже у вашего клиента. Возникает естественный вопрос: почему этот кто-то не создал их сразу нормальными? И кто будет инициализировать объекты, если они понадобятся в другом месте?
А>>ИМХО существование объекта в невалидном состоянии между вызовами публичных методов — плохая практика. N_C>ИМХО практика нормальная. К примеру как при работе с БД — у объекта Connection есть конструктор, а есть метод Open. Таких ситуаций множество можно описать.
Вы тут подменяете инициализацию и открытие соединения. Объект соединение в закрытом состоянии это вполне валидный объект, с явно определенным поведением. И открытие соединения это часть интерфейса.
Похоже термин "инициализация объекта" мы наделяем разными понятиями. Я под этим термином понимаю некое наполнение внутреннего состояния объекта. И это состояние должно быть валидным с точки зрения публичного интерфейса.
Re[2]: Стоит ли переносить инициализацию в отдельный метод?
А>>Есть ли у этого подхода преимущества перед обычной инициализацией через конструктор в C#? S>It depends. Например, если инициализация включает в себя логику, специфичную для отдельных сценариев, вместо двух конструкторов удобней использовать static-методы вида CreateForA(), CreateForB().
Это до того момента когда вам тесты понадобится писать. В целом для нормально тестируемого кода надо забыть что такое статический метод, или статическая переменная
Здравствуйте, drol, Вы писали:
D>Это верно только для однопоточной ситуации. В многопоточном случае возможны такие "чудеса", как выполнение конструктора параллельно с финализатором.
Пруф?
Финализаторы и так в отдельном потоке вызываются. Что вы подразумеваете под словом "многопоточный" в данном случае?
Re[3]: Стоит ли переносить инициализацию в отдельный метод?
От:
Аноним
Дата:
02.07.13 10:22
Оценка:
Здравствуйте, Аноним, Вы писали:
А>А можете раскрыть мысль? Совершенно не улавливается связь между инициализацией и интерфейсами.
Для инициализации используется конструктор. Конструктор не может быть определен в интерфейсе.
Re[5]: Стоит ли переносить инициализацию в отдельный метод?
От:
Аноним
Дата:
02.07.13 10:28
Оценка:
Здравствуйте, Аноним, Вы писали:
N_C>>ИМХО практика нормальная. К примеру как при работе с БД — у объекта Connection есть конструктор, а есть метод Open. Таких ситуаций множество можно описать. А>Вы тут подменяете инициализацию и открытие соединения. Объект соединение в закрытом состоянии это вполне валидный объект, с явно определенным поведением. И открытие соединения это часть интерфейса.
Почему?
Допустим, имеется объект view сразу после вызова new View(). И имеется объект view после вызова view.Initialize(some data), заполненный некоторыми данными.
Что невалидного в состоянии объекта view без данных?
Re[2]: Стоит ли переносить инициализацию в отдельный метод?
От:
Аноним
Дата:
02.07.13 10:35
Оценка:
Первое.
Точнее, так: в чем преимущество:
public class MyClass
{
public MyClass()
{
}
public MyClass Initialize(...);
}
перед:
public class MyClass
{
public MyClass(...)
{
}
}
Re[3]: Стоит ли переносить инициализацию в отдельный метод?
Здравствуйте, Аноним, Вы писали:
А>Точнее, так: в чем преимущество: А> public MyClass Initialize(...); А>перед: А> public MyClass(...)
Отложенная инициализация. Удобно, когда нужно инстанцинировать обьект, а инициализировать попозже (не в момент инстанцинации). Частный случай применения — если инстанцинация относительно "дорого" обходится, т.к. в этом случае есть возможность ее избежать, вызвав Deinit+Init.
---
ПроГLамеры объединяйтесь..
Re[3]: Стоит ли переносить инициализацию в отдельный метод?
Т.е., фабричный метод.
А>Точнее, так: в чем преимущество: А>перед:
Объект создан, но не инициализирован. Т.е. пользоваться им нельзя. Вопросы: 1) зачем его создавать? 2) тут есть преимущество?
Соседний пост про отложенную инициализацию не катит. Отложенная инициализация должна быть скрыта от внешнего кода.
Re[4]: Стоит ли переносить инициализацию в отдельный метод?
Однажды такое понадобилось, чтобы запретить клиентам наследовать от моего абстрактного класса Either — т.е. сделать закрытую иерархию (эмуляция алгебраических типов):
Скрытый текст
public abstract class Either<T1, T2>
{
private Either()
{ }
public static Either<T1, T2> New(T1 value)
{
return Value1(value);
}
public static Either<T1, T2> New(T2 value)
{
return Value2(value);
}
public static Either<T1, T2> Value1(T1 value)
{
return new Either1(value);
}
public static Either<T1, T2> Value2(T2 value)
{
return new Either2(value);
}
public abstract T Exec<T>(Func<T1, T> f1, Func<T2, T> f2);
public abstract void Exec(Action<T1> f1, Action<T2> f2);
private sealed class Either1 : Either<T1, T2>
{
private readonly T1 _value;
public Either1(T1 value)
{
_value = value;
}
public override T Exec<T>(Func<T1, T> f1, Func<T2, T> f2)
{
return f1(_value);
}
public override void Exec(Action<T1> f1, Action<T2> f2)
{
f1(_value);
}
}
private sealed class Either2 : Either<T1, T2>
{
private readonly T2 _value;
public Either2(T2 value)
{
_value = value;
}
public override T Exec<T>(Func<T1, T> f1, Func<T2, T> f2)
{
return f2(_value);
}
public override void Exec(Action<T1> f1, Action<T2> f2)
{
f2(_value);
}
}
}
Re[5]: Стоит ли переносить инициализацию в отдельный метод?
Здравствуйте, 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[4]: Стоит ли переносить инициализацию в отдельный метод?
От:
Аноним
Дата:
03.07.13 03:34
Оценка:
Здравствуйте, abibok, Вы писали:
A>А есть тяжелый код с выделением ресурсов, так что конструктор выглядит явно переусложненным. Если такой конструктор бросит исключение в using (Foo foo = new Foo()), то Dispose не будет вызван и ресурсы останутся неосвобожденными.
Спасибо. Именно этот момент и интересовал.
Просьба пояснить. A>>>Если код инициализации может бросать исключения, то лучше вынести его в отдельный метод. Особенно если класс реализует IDisposable.
Если код инициализации не выделяет ресурсов (но может бросать исключения), то почему лучше тоже вынести его в отдельный метод?
Re[6]: Стоит ли переносить инициализацию в отдельный метод?
Здравствуйте, abibok, Вы писали:
A>А представьте себе, что вы создаете объект некоторого класса не через new, а используя reflection. Исключение из конструктора завернется в какой-нибудь TargetInvocationException и настоящий call stack потеряется. А если хочется перехватить исключение до того, как оно было брошено (чтобы сохранить хороший дамп, например), то вместо простого создания объекта через активатор придется делать вот так:
[километр кода скипнут]
Или просто посмотреть содержимое ex.InnerException
Здравствуйте, igor-booch, Вы писали:
IB>skipped
"Неинициализированный" (то, о чем пишет TK) и "недостижимый" (то, что делает объект целью GC) — это разные вещи. Ждем пруфлинков.
HL>"Неинициализированный" (то, о чем пишет TK) и "недостижимый" (то, что делает объект целью GC) — это разные вещи. Ждем пруфлинков.
Я имел ввиду что возможность доступа к не инициализированному объекту это ступенька к недостижимости до не инициализированного объекта.
Если доступ к не инициализированному в конструкторе объекту невозможен, то и невозможна потеря ссылки на этот объект до конца его инициализации.
Так как сначала мы получаем доступ (присваиваем переменной ссылку на экземпляр), и только потом мы можем потерять доступ, присвоив переменной ссылку на другой экземпляр (или null).
Отсутствие ссылок на экземпляр это его недостижимость.
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания http://rsdn.ru/Info/rules.xml
Re[6]: Конструктор не бросает исключений, не связанных с проверкой аргументов
.
Какие еще могут быть проблемы?
А Initialize(...) тоже не может выкидывать исключения не связанные с проверкой аргументов?
Проверка аргументов понятие растяжимое.
Она может быть любой сложности.
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания http://rsdn.ru/Info/rules.xml
Re[6]: Стоит ли переносить инициализацию в отдельный метод?
BB>Вы уверены, что checkInitializedThread увидит значение foo, отличное от первого new Foo()?
Хороший вопрос, Вы наверное имеете ввиду Access to modified closure.
Проверил, нет, же, видит прекрасно новые Foo.
И Resharper не ругается на Access to modified closure.
Сделал предположение, что Access to modified closure возникает только если переменная foo будет меняться в том же контексте, в котором создавалась лямбда её использующая:
Foo foo = new Foo();
int i = 0;
Thread checkInitializedThread = new Thread(() =>
{
do
{
if (!foo._initialized1 | !foo._initialized2)
{
Console.WriteLine("Bingo!!!");
}
Console.WriteLine(foo._i);
} while (true);
});
checkInitializedThread.Start();
do
{
foo = new Foo();
foo._i = i;
i++;
} while (true);
class Foo
{
public bool _initialized1;
public bool _initialized2;
public int _i;
public Foo()
{
//Thread.Sleep(10);
_initialized1 = true;
Thread.Sleep(10);
_initialized2 = true;
}
}
Resharper заругался на Access to modified closure. Но checkInitializedThread все-равно видит новые Foo
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания http://rsdn.ru/Info/rules.xml
Здравствуйте, igor-booch, Вы писали:
BB>>Вы уверены, что checkInitializedThread увидит значение foo, отличное от первого new Foo()?
IB>Хороший вопрос, Вы наверное имеете ввиду Access to modified closure. IB>Проверил, нет, же, видит прекрасно новые Foo. IB>И Resharper не ругается на Access to modified closure.
Нет, я просто не соображу, где там барьер, который гарантирует видимость другим потоком.
Здравствуйте, bl-blx, Вы писали:
BB>второй поток делает non-volatile read — с чего бы ему в main memory лезть?
Совершенно верно. В потоке checkInitializedThread и foo, и foo._initialized1, и foo._initialized2, и foo._i могут быть закэшированы. Сделать это способна любая фаза кодогенерации: IL, JIT и т.д.
Re[8]: Стоит ли переносить инициализацию в отдельный метод?
Здравствуйте, abibok, Вы писали:
A>>>Для исключения в конструкторе это не подойдет. A>>Ну тогда и твой вариант не подойдет
A>А мой как раз подойдет.
В чем будет отличие от моего?
A>Я плотно занимался этой темой, знаю кучу нюансов по поводу бросания и перехвата исключений, и этот код сейчас работает.
И на каждый вызов делает компиляцию создаваемого Expression-а?
Re[7]: Конструктор не бросает исключений, не связанных с проверкой аргументов
IB>А Initialize(...) тоже не может выкидывать исключения не связанные с проверкой аргументов?
Может, это же обычный метод. И его исключения не завернут в TargetInvocationException.
IB>Проверка аргументов понятие растяжимое. IB>Она может быть любой сложности.
Здесь нет абсолютного решения — только конструктор или только отдельный метод. Нужно смотреть по ситуации. Я просто показал, что есть ситуации, когда наличие метода оправдано.
Re[11]: Стоит ли переносить инициализацию в отдельный метод?
A>>А мой как раз подойдет. A>В чем будет отличие от моего?
В вашем случае (кстати, дайте пример вашего кода) исключение в конструкторе ловится внутри CLR, заворачивается в TargetInvocationException и перебрасывается. Все, куча информации потеряна. Например, значения локальных переменных в момент бросания уже не получить.
A>>Я плотно занимался этой темой, знаю кучу нюансов по поводу бросания и перехвата исключений, и этот код сейчас работает. A>И на каждый вызов делает компиляцию создаваемого Expression-а?
Да. Это большая проблема? Мы не обязаны пользоваться этой функцией для создания абсолютно всех объектов. А там где это необходимо, потеря производительности (не такая уж и большая) оправдана.
Re[8]: Стоит ли переносить инициализацию в отдельный метод?
Здравствуйте, abibok, Вы писали:
S>>Или просто посмотреть содержимое ex.InnerException
A>Вы не совсем понимаете глубину проблемы. В моей задаче InnerException не поможет.
Задачу вы пока не озвучили
Если как написано в другом посте, проблема в отладке — в настройках (debug-exceptions) можно включить прерывание отладки в момент броска исключения.
Re[12]: Стоит ли переносить инициализацию в отдельный метод?
Здравствуйте, abibok, Вы писали: A>В вашем случае (кстати, дайте пример вашего кода) исключение в конструкторе ловится внутри CLR, заворачивается в TargetInvocationException и перебрасывается. Все, куча информации потеряна. Например, значения локальных переменных в момент бросания уже не получить.
О чем это ты, можно поподробнее?
Вот код:
Скрытый текст
public static class ExceptionHelper
{
private static readonly Action<Exception> _preserveInternalException;
static ExceptionHelper()
{
var preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace", BindingFlags.Instance | BindingFlags.NonPublic);
_preserveInternalException = (Action<Exception>)Delegate.CreateDelegate(typeof(Action<Exception>), preserveStackTrace);
}
public static Exception PreserveStackTrace(this Exception ex)
{
_preserveInternalException(ex);
return ex;
}
}
public class A
{
public A(int x, string s)
{
throw new ArgumentException("s");
}
}
static class Program
{
public static object CreateInstance(Type type, object[] arguments)
{
try
{
return Activator.CreateInstance(type, arguments);
}
catch (TargetInvocationException ex)
{
throw ex.InnerException.PreserveStackTrace();
}
}
private static void Main()
{
try
{
//var a = new A(42, "Hi");var a = CreateInstance(typeof(A), new object[] { 42, "Hi" });
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
В консоль выводится:
System.ArgumentException: s
at ConsoleApplication46.A..ctor(Int32 x, String s) in d:\tmp\ConsoleApplication46\ConsoleApplication46\Program.cs:line 34
at ConsoleApplication46.Program.CreateInstance(Type type, Object[] arguments) in d:\tmp\ConsoleApplication46\ConsoleApplication46\Program.cs:line 56
at ConsoleApplication46.Program.Main() in d:\tmp\ConsoleApplication46\ConsoleApplication46\Program.cs:line 80
A>>>Я плотно занимался этой темой, знаю кучу нюансов по поводу бросания и перехвата исключений, и этот код сейчас работает. A>>И на каждый вызов делает компиляцию создаваемого Expression-а? A>Да. Это большая проблема? Мы не обязаны пользоваться этой функцией для создания абсолютно всех объектов. А там где это необходимо, потеря производительности (не такая уж и большая) оправдана.
А че б не закэшировать активаторы? Ну да фиг с ним...
Re[13]: Стоит ли переносить инициализацию в отдельный метод?
Представьте себе, что конструктор не такой простой, а имеет сложную внутреннюю логику, которая зависит от состояния членов класса, параметров и значений локальных переменных.
Мы хотим всю эту информацию сохранить в лог, а разработчику предоставить managed dump, который будет указывать прямо на исходное исключение, а не на место, где это исключение поймали и перевыбросили.
Более того, мы хотим иметь не только стек вызовов, который привел к исключению — А(B(C())), но и историю выполнения — X->Y->Z->A(B(C())). И если исключение произошло из-за значения члена класса _myField, то проследить по истории до места, где _myField стал таким, и это место тоже включить в лог. А потом автоматически сделать root cause analysis "из-за проблемы в методе X у нас возникают исключения в C, T и Q".
Это общая задача, которая должна решаться для любого метода. Просто конструкторы немного особенные и создание объектов при таком подходе требуют специального обращения. A>В консоль выводится: A>
A>System.ArgumentException: s
A> at ConsoleApplication46.A..ctor(Int32 x, String s) in d:\tmp\ConsoleApplication46\ConsoleApplication46\Program.cs:line 34
A> at ConsoleApplication46.Program.CreateInstance(Type type, Object[] arguments) in d:\tmp\ConsoleApplication46\ConsoleApplication46\Program.cs:line 56
A> at ConsoleApplication46.Program.Main() in d:\tmp\ConsoleApplication46\ConsoleApplication46\Program.cs:line 80
Мне кажется или у вас с номерами строк не все в порядке?
A>А че б не закэшировать активаторы? Ну да фиг с ним...
Здравствуйте, Sinix, Вы писали:
S>Здравствуйте, HowardLovekraft, Вы писали:
HL>>Если в какой-то момент времени выполняется конструктор объекта, это значит, что он очень даже достижим.
S>Необязательно. S>http://ericlippert.com/2013/06/10/construction-destruction/
Я правильно понимаю, что в общем виде мы не можем обеспечить гарантию освобождения unmanaged ресурсов?
void F()
{
new SomeClassWithUnmanagedResources();
}
Finalizer может закончить работу до того, как в конструкторе будут выделены и сохранены в полях unmanaged ресурсы, так?
Мне, как разработчику SomeClassWithUnmanagedResources, что делать, чтобы обеспечить гарантию их освобождения?
Re[10]: Стоит ли переносить инициализацию в отдельный метод?
Здравствуйте, abibok, Вы писали:
S>>Задачу вы пока не озвучили
A>Задача — сохранить максимум информации о контексте приложения в момент возникновения исключительной ситуации для логирования и последующей отладки. Это включает в себя правильный callstack со всей информацией, включая значения локальных переменных и параметров функций в каждом фрейме, а не только текстовое представление стека вызовов. Для этого исключение нужно перехватывать сразу после того, как оно было брошено и до того, как оно войдет в блок catch.
Это что-то фирменное. Можно показать?
Re[11]: Стоит ли переносить инициализацию в отдельный метод?
Это очень круто в плане debuggability, позволяет избавиться от необходимости постоянно логировать всякие мелочи, работает прозрачно для существующего и нового кода.
Показать не могу.
Re[12]: Стоит ли переносить инициализацию в отдельный метод?
Здравствуйте, abibok, Вы писали:
A>>Это что-то фирменное. Можно показать?
A>Это очень круто в плане debuggability, позволяет избавиться от необходимости постоянно логировать всякие мелочи, работает прозрачно для существующего и нового кода. A>Показать не могу.
hackability?
Забавно, что в качестве аргумента ты используешь то, что показать не можешь...
Re[13]: Стоит ли переносить инициализацию в отдельный метод?
и думает, что все безопасно, привилегии не уйдут вызывающему. А мы подсунем такой x, который приведет к исключению, и перехватим его до входа в finally, и вернемся в свой код ненадого.
A>Забавно, что в качестве аргумента ты используешь то, что показать не можешь...
В качестве аргумента я описываю реальную задачу, пусть и не очень распространенную (что нормально для всякой достаточно сложной задачи), а вот реализацию показать не могу. Но это не отменяет существование задачи и проблемы.
Re[14]: Стоит ли переносить инициализацию в отдельный метод?
Здравствуйте, abibok, Вы писали:
A>>hackability?
A>В том числе, когда это касается воровства контекста повышенных привилегий. Вызываем мы "некоторый" метод как юзер, а он внутри делает
A>
A>public void Foo(Something x)
A>try
A>{
A> GiveMeAdminPrivileges();
A> Process(x);
A>}
A>finally
A>{
A> RevertToUserPrivileges();
A>}
A>
A>и думает, что все безопасно, привилегии не уйдут вызывающему. А мы подсунем такой x, который приведет к исключению, и перехватим его до входа в finally, и вернемся в свой код ненадого.
Интересно. А если Foo лежит в подписанной сборке в GAC, тоже перехватите? А Code Access Security тоже умеете обходить?
A>>Забавно, что в качестве аргумента ты используешь то, что показать не можешь... A>В качестве аргумента я описываю реальную задачу, пусть и не очень распространенную (что нормально для всякой достаточно сложной задачи), а вот реализацию показать не могу. Но это не отменяет существование задачи и проблемы.
Описание задачи и ее существование не может быть аргументом.
Re[15]: Стоит ли переносить инициализацию в отдельный метод?
Здравствуйте, drol, Вы писали:
D>Кэш данных в архитектурах x86/x64 когерентен. И к обсуждаемым вопросам не имеет никакого отношения.
Не для холивара
Ок, а чем ещё можно объяснить, что пример igor-booch (чуть допилил) не падает?
class Program
{
static Foo foo = new Foo(10000);
static void Main(string[] args)
{
Thread instantiationThread = new Thread(() =>
{
do
{
foo = new Foo(10000);
} while (true);
});
Thread checkInitializedThread = new Thread(() =>
{
do
{
var local = foo;
if (!local._initialized1 | !local._initialized2 | local._val == 0)
{
Console.WriteLine("Bingo!!!");
}
} while (true);
});
instantiationThread.Start();
checkInitializedThread.Start();
Console.ReadKey();
}
class Foo
{
private static readonly Random rnd = new Random(0);
public bool _initialized1;
public bool _initialized2;
public int _val;
public Foo(int count)
{
//Thread.Sleep(10);
_initialized1 = true;
//Thread.Sleep(10);
_initialized2 = true;
_val = rnd.Next(count) + 1;
}
}
}
Здравствуйте, Sinclair, Вы писали:
S>Чтобы финализатор начал выполняться параллельно с конструктором, нужно, чтобы поток исполнения конструктора дошёл до места, после которого нет обращений к this.
Ну да. В чём проблема-то ?
S>Это означает, что если у нас есть недоинициализированный экземпляр, то ссылка на него удерживается в рутах GC, и в лапы финализатора он не попадёт.
Ты как-то узко понимаешь инициализацию. Вот есть поле. Конструктор проставил в него ссылку на объект. А теперь вызывает какие-то методы этого объекта. С этого момента зависимости по this уже нет. Однако инициализация идёт во всю.
Re[15]: Стоит ли переносить инициализацию в отдельный метод?
Здравствуйте, artelk, Вы писали:
A>>и думает, что все безопасно, привилегии не уйдут вызывающему. А мы подсунем такой x, который приведет к исключению, и перехватим его до входа в finally, и вернемся в свой код ненадого. A>Интересно. А если Foo лежит в подписанной сборке в GAC, тоже перехватите? А Code Access Security тоже умеете обходить?
Ну конечно же да. Они же явно пользуются Debugger Services и шаманством с CLR Host, а там all bets are off.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[2]: Стоит ли переносить инициализацию в отдельный метод?
Здравствуйте, HowardLovekraft, Вы писали:
HL>Здравствуйте, Аноним, Вы писали:
А>>Есть ли у этого подхода преимущества перед обычной инициализацией через конструктор в C#? HL>Что имеется ввиду? Это: HL>
Re[3]: Стоит ли переносить инициализацию в отдельный метод?
От:
Аноним
Дата:
04.07.13 10:05
Оценка:
Tom>использовать static-методы вида CreateForA(), CreateForB(). Tom>Это до того момента когда вам тесты понадобится писать. В целом для нормально тестируемого кода надо забыть что такое статический метод, или статическая переменная
А причем тут тесты ? В чем подвох ?
Re[3]: Стоит ли переносить инициализацию в отдельный метод?
Здравствуйте, Tom, Вы писали:
Tom>Это до того момента когда вам тесты понадобится писать. В целом для нормально тестируемого кода надо забыть что такое статический метод, или статическая переменная
Ну, без фанатизма.
В данном случае, статический метод ничем не отличается от конструктора. Если у тебя код организован так, что экземпляр этого класса ты инжектишь, а не создаешь прямо в том классе, который хочешь оттестировать, то совершенно пофиг как ты его создаешь в боевом коде, через статический метод или честный конструктор, на тестировании это никак не скажется.