Re[4]: UninitializedException
От: drol  
Дата: 02.07.13 13:59
Оценка:
Здравствуйте, HowardLovekraft, Вы писали:

HL>Пруф?


Читайте спецификацию... Ну и Липперта...

HL>Что вы подразумеваете под словом "многопоточный" в данном случае?


Вызов методов объекта в разных потоках. Никаких гарантий синхронизации для вызова конструктора не даётся. В этом плане он ничем не отличается от прочих instance-методов.
Re: Стоит ли переносить инициализацию в отдельный метод?
От: abibok  
Дата: 02.07.13 15:00
Оценка:
А>Есть ли у этого подхода преимущества перед обычной инициализацией через конструктор в C#?

Если код инициализации может бросать исключения, то лучше вынести его в отдельный метод. Особенно если класс реализует IDisposable.
Re[2]: Стоит ли переносить инициализацию в отдельный метод?
От: artelk  
Дата: 02.07.13 15:08
Оценка:
Здравствуйте, abibok, Вы писали:

A>Если код инициализации может бросать исключения, то лучше вынести его в отдельный метод.


Ага,
class A
{
  A(ISomething s)
  {
    if(s == null)
      throw new ArgumentNullException("s");
    this.s = s;
  }
}

Срочно вынести в отдельный метод!
Re[3]: Стоит ли переносить инициализацию в отдельный метод?
От: abibok  
Дата: 02.07.13 16:56
Оценка:
A>Срочно вынести в отдельный метод!

Не надо поясничать. Есть проверка аргументов и примитивная логика инициализации вроде присвоения начальных значений полям. Этому делу самое место в конструкторе.
А есть тяжелый код с выделением ресурсов, так что конструктор выглядит явно переусложненным. Если такой конструктор бросит исключение в using (Foo foo = new Foo()), то Dispose не будет вызван и ресурсы останутся неосвобожденными.
Re[4]: Стоит ли переносить инициализацию в отдельный метод?
От: artelk  
Дата: 02.07.13 17:29
Оценка:
Здравствуйте, 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;
    }
  }

  private void Initialize()
  {
    //тяжелый код с выделением ресурсов
  }

  public void Dispose()
  {
    if(disposed)
      return;
    //проверяем, какие ресурсы были выделены и освобождаем их
  }
}
?

Это избавило бы клиентский код от необходимости вызывать Initialize().
Более того, в классе Foo не пришлось бы делать дополнительных приседаний, чтобы корректно работать в ситуации со множественными вызовами Initialize() — метод торчит наружу, никто не запрещает его вызвать дважды.
Re[5]: Стоит ли переносить инициализацию в отдельный метод?
От: abibok  
Дата: 02.07.13 17:50
Оценка:
A>Метод Initialize может быть и не вызван вовсе.

Вы подменяете одну проблему другой. Речь шла о дизайне конструктора, а не о контроле за правильностью соблюдения контракта класса.

A>Или в нем в середине было выкинуто исключение и не все ресурсы были выделены. Поэтому в Dispose нужно вставить проверки вида


Конечно, а разве вы делаете иначе? Знаете чем опасны исключения, брошенные в Dispose в частности и в finally вообще?
Re[6]: Стоит ли переносить инициализацию в отдельный метод?
От: artelk  
Дата: 02.07.13 18:12
Оценка:
Здравствуйте, abibok, Вы писали:

A>>Метод Initialize может быть и не вызван вовсе.

A>Вы подменяете одну проблему другой. Речь шла о дизайне конструктора, а не о контроле за правильностью соблюдения контракта класса.
Разве?

Есть ли у этого подхода преимущества перед обычной инициализацией через конструктор в C#?

Где тут про дизайн конструктора? Здесь альтернатива конструктор vs отдельный метод инициализации.
Re[7]: Стоит ли переносить инициализацию в отдельный метод?
От: abibok  
Дата: 02.07.13 18:34
Оценка: :)
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]: Стоит ли переносить инициализацию в отдельный метод?
От: abibok  
Дата: 03.07.13 05:22
Оценка: 2 (1)
А>Просьба пояснить.
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]: Стоит ли переносить инициализацию в отдельный метод?
От: Sinix  
Дата: 03.07.13 06:09
Оценка:
Здравствуйте, abibok, Вы писали:

A>А представьте себе, что вы создаете объект некоторого класса не через new, а используя reflection. Исключение из конструктора завернется в какой-нибудь TargetInvocationException и настоящий call stack потеряется. А если хочется перехватить исключение до того, как оно было брошено (чтобы сохранить хороший дамп, например), то вместо простого создания объекта через активатор придется делать вот так:


[километр кода скипнут]

Или просто посмотреть содержимое ex.InnerException
Re[5]: UninitializedException
От: HowardLovekraft  
Дата: 03.07.13 07:55
Оценка: -1
Здравствуйте, drol, Вы писали:

D>Читайте спецификацию...

Спецификацию чего (ссылку на документ + номер пункта/параграфа)?
D>Ну и Липперта...
Ссылку на пост, в котором это описано, пожалуйста.

Утверждение, что CLR может выполнить вызов финализатора для объекта, у которого в данный момент времени выполняется конструктор, выглядит несколько... хм... нелогичным.
Чтоб CLR позвала финализатор, должна произойти сборка мусора, после которой ссылка на объект будет помещена в F-reachable queue. Соответственно, объект должен быть недостижим. Если в какой-то момент времени выполняется конструктор объекта, это значит, что он очень даже достижим.

D>Вызов методов объекта в разных потоках. Никаких гарантий синхронизации для вызова конструктора не даётся

Приведите пример кода, в котором (хотя бы потенциально) может возникнуть одновременный вызов конструктора у одного и того же объекта.
Re[6]: Воспроизвести не вышло
От: igor-booch Россия  
Дата: 03.07.13 09:16
Оценка: :)
Как не старался воспроизвести то, о чем говорит 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/forum/dotnet/5200476.1
Автор: TK
Дата: 14.06.13

может это навеяло http://msdn.microsoft.com/en-us/magazine/jj863136.aspx
?
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания
http://rsdn.ru/Info/rules.xml
Re[7]: Воспроизвести не вышло
От: HowardLovekraft  
Дата: 03.07.13 10:28
Оценка:
Здравствуйте, igor-booch, Вы писали:

IB>skipped

"Неинициализированный" (то, о чем пишет TK) и "недостижимый" (то, что делает объект целью GC) — это разные вещи. Ждем пруфлинков.
Re[8]: Воспроизвести не вышло
От: igor-booch Россия  
Дата: 03.07.13 10:39
Оценка:
HL>"Неинициализированный" (то, о чем пишет TK) и "недостижимый" (то, что делает объект целью GC) — это разные вещи. Ждем пруфлинков.

Я имел ввиду что возможность доступа к не инициализированному объекту это ступенька к недостижимости до не инициализированного объекта.
Если доступ к не инициализированному в конструкторе объекту невозможен, то и невозможна потеря ссылки на этот объект до конца его инициализации.
Так как сначала мы получаем доступ (присваиваем переменной ссылку на экземпляр), и только потом мы можем потерять доступ, присвоив переменной ссылку на другой экземпляр (или null).
Отсутствие ссылок на экземпляр это его недостижимость.
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания
http://rsdn.ru/Info/rules.xml
Re[8]: Пускай лучше drol приведет пример кода
От: igor-booch Россия  
Дата: 03.07.13 10:47
Оценка: -1
HL>"Неинициализированный" (то, о чем пишет TK) и "недостижимый" (то, что делает объект целью GC) — это разные вещи. Ждем пруфлинков.
Написать в интернетах могут все-что угодно. Пример кода это железный proof.
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания
http://rsdn.ru/Info/rules.xml
Re[6]: UninitializedException
От: Sinix  
Дата: 03.07.13 11:03
Оценка: 4 (1) +1
Здравствуйте, HowardLovekraft, Вы писали:

HL>Если в какой-то момент времени выполняется конструктор объекта, это значит, что он очень даже достижим.


Необязательно.
http://ericlippert.com/2013/06/10/construction-destruction/
Re[6]: Конструктор не бросает исключений, не связанных с проверкой аргументов
От: igor-booch Россия  
Дата: 03.07.13 11:22
Оценка:
A>Потому что на практике ожидается, что конструктор не бросает исключений, не связанных с проверкой аргументов.

Почему? Проблема с Dispose решена здесь
Автор: artelk
Дата: 02.07.13
.
Какие еще могут быть проблемы?
А Initialize(...) тоже не может выкидывать исключения не связанные с проверкой аргументов?
Проверка аргументов понятие растяжимое.
Она может быть любой сложности.
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания
http://rsdn.ru/Info/rules.xml
Re[6]: Стоит ли переносить инициализацию в отдельный метод?
От: artelk  
Дата: 03.07.13 11:54
Оценка:
Здравствуйте, abibok, Вы писали:

A>А представьте себе, что вы создаете объект некоторого класса не через new, а используя reflection. Исключение из конструктора завернется в какой-нибудь TargetInvocationException и настоящий call stack потеряется.

Есть несколько решений этой проблемы:
http://stackoverflow.com/questions/57383/in-c-how-can-i-rethrow-innerexception-without-losing-stack-trace
Re[7]: Воспроизвести не вышло
От: bl-blx Россия http://yegodm.blogspot.com
Дата: 03.07.13 12:13
Оценка:
Здравствуйте, igor-booch, Вы писали:

IB>Как не старался воспроизвести то, о чем говорит drol не вышло

IB>
IB>Foo foo = new Foo();
IB>Thread instantiationThread = new Thread(() =>
IB>{
IB>    do
IB>    {
IB>        foo = new Foo();
IB>    } while (true);
IB>});

IB>Thread checkInitializedThread = new Thread(() =>
IB>{
IB>    do
IB>    {
IB>        if (!foo._initialized1 | !foo._initialized2)
IB>        {
IB>            Console.WriteLine("Bingo!!!");
IB>        }
IB>    } while (true);
IB>});

IB>instantiationThread.Start();
IB>checkInitializedThread.Start();
IB>


Вы уверены, что checkInitializedThread увидит значение foo, отличное от первого new Foo()?
El pueblo unido jamás será vencido.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.