Pull to refresh

Comments 27

Помню в бытность изучения платформы наткнулся на такое, когда пытался поправить Location у контрола в WinForms: someControl.Location.X++; (где Location типа Point, который, ессесно, struct).
А так всё описанное должен знать любой дотнетчик. (хотя бы для прохождения собеседования).
Ну, можно провести опрос, но мне как-то сомнительно, что много людей скажут точное поведение для 2-го и 3-го случаев, т.е. для readonly и массива и тем более за трюк с анонимным классом.

И я никого не знаю, кто такую жесть спрашивает на собеседовании:) (помимо первого случая).
ой там где я бывал — всегда любили попытаться опустить кандидата — к примеру, на знание замыканий, дабы он (кандидат) уж слишком много не требовал
Ну, хз. Мне часто говорят, что я задаю сложные вопросы на собеседовании, но про устройство замыканий я никогда не спрашивал, и подобный хард-кор, как приведенные выше примеры (кроме первого случая) — тоже.
ну в любом случае, да, кроме первого (и второго собсно) вашего пункта в моём комменте «должен» надо заменить на «не плохо бы знать, что такое есть».
Хотя про большие угрозы со стороны изменяемых типов-значений я наслышан, было интересно посмотреть ещё несколько примеров, где могут всплыть проблемы. Спасибо!
Ага, не за что!
Проблемы, связанные с изменением структуры через интерфейс довольно известны (спасибо дядьке Рихтеру), а вот многие другие — известны не так уж и многим…
Да. Именно от Рихтера узнал про такие неприятности. А примеры действительно толковые, сам не напарывался на такое. И тут с одной стороны «слава богу», а с другой плохо, так как не обратил бы внимание на опасный код. Собственно, в первом примере из статьи я долго не мог заметить проблемы. Открыл VS, накатал пример (не копипастом), всё равно компилился. И только потом заметил, в чём мой код отличался от кода из статьи… Ну а уже после включения внимательности в остальных примерах угрозы я заметил.

На самом деле, я стал себе делать памятки о том, как какие единицы кода реализовывать. Для структур в частности, записано, что они должны быть неизменяемыми (read-only поля спасают) в общем случае (ну и кое-что ещё типа небольшого размера, переопределение Equals и прочего).
Большое спасибо, отличная статья!
Спасибо, годная статья. По поводу опасности при реализации IDisposable в значимом типе, к своему стыду не знал.
Значимый тип (Value Type) называется таковым именно потому, что передается по значению. Размещение в памяти вторично, например, если у ссылочного типа (reference type) будет поле типа int (value type), то это поле будет находится в куче вместе со ссылочным типом.
Не в обиду будет сказано: а вы статью читали? Там ведь все именно так и сказано, причем практически такими же словами:)))
Читал, там написанно что одним из свойств значимых типов является то, что о ни передаются по значению. А на самом деле это определение значимых типов. Кроме того по умолчанию в стэке располагаются только локальные переменные значимых типов.

Эрика Липперт (один из людей, который работает над C#) об этом этом писал в своем блоге. (осторожно, английский)
Да, я не особенно боюсь блога Эрика, будучи его переводчиком на русский язык (сори, задержался с последними статьями, скоро догоню:) ).

Речь о том, что обе эти особенности в статье описаны, и то, что значимые типы копируются, и то, что это свойство следует из названия и то, что они (значимые типы) могут располагаться и в стеке, и в куче (да, про регистры я тоже помню, но специально опустил, чтобы не отвлекаться на не нужные темы).

Вопрос в том, для чего комментарий?
На самом деле, о вопросе размещения значимых и референс типов действительно нужно останавливаться более подробно. Я часто провожу собеседования, и всегда задаю вопрос о различии структур и классов. Так вот, все как один отвечают что «структуры хранятся в стеке, а классы — в куче». Когда им приводишь такой пример
class A
{
int i;
}
и спрашиваешь, где находится i — большинство кандидатов впадают в ступор.
Да с этим никто не спорит, но статья-то не об этом. А раз не об этом, то максимум, что можно (и нужно сделать), это упомянуть об особенностях месторасположения экземпляров тех и других типов.
Ну, это вопрос на «поговорить за жизнь», практического значения это знание ИМХО не имеет.
var x = new { Items = new List<int> { 1, 2, 3 }.GetEnumerator() };
while (x.Items.MoveNext())
 {
   Console.WriteLine(x.Items.Current);
 }

Ух ты, не знал, что Current по умолчанию ноль возвращает :).

За статью спасибо, очень правильная!
Ну, по умолчанию свойство Current енумератора блока итератора возвращает не 0, а default(T), а поскольку для целого числа — это 0, вот он 0 и возвращает.

А если формально, то в данном случае проявляются любимое всеми С++-никами неопределенное поведение:)

When an enumerator object is in the suspended state, the value of Current is the value set
by the previous call to MoveNext. When an enumerator object is in the before, running, or
after states, the result of accessing Current is unspecified.


Например, в некоторых ручных реализациях итераторов (а не в блоках итераторов) бросается InvalidOperationException.
Ну с итераторами бывают сюрпризы, особенно с замыканиями, а уж тем более в ручных реализациях.
Хм, а какая связь между итераторами и замыканиями?
Не столько именно связь между итераторами и замыканиями, сколько могут быть просто неочевидные ситуации с использованием обоих. Даже если мы знаем, что замыкания, эмм.., собственно, замыкаются над переменными, а не их значениями, некоторый интуитивно написанный код может иметь непредсказуемый результат:

var closures = new List<Func<int>>();
var items = new List<int> { 1, 2, 3 };

foreach (var item in items)
{
    closures.Add(() => item);
}

closures.ForEach(c => Console.WriteLine(c.Invoke()));
Это да, возможно ты (я надеюсь можно на ты?:) ) в предыдущем комментарии выразился не совсем ясно. Было впечатление, что речь идет о замыкании итеоратов или о замыкании в итераторах. А то, что в обоих этих случаях нужно ясно представлять, что происходит — это да, я согласен.
Конечно, можно на ты :)

Да, я, пожалуй, не вполне ясно выразился. Речь именно о том, что некая синтаксическая конструкция требует знания того, как это происходит «за кулисами», чтобы ее нормально использовать. Вот вроде, foreach и foreach себе — отличный и часто используемый синтаксический подсластитель, а на практике надо понимать, что переменная «item» объявляется за пределами цикла, что может быть не вполне очевидно. Прекрасно помню свои «WTF» в попытках найти подобные проблемы (в обнимку с дебаггерами и рефлекторами), код вроде верный, а тесты падают.

Конечно, сейчас уже ReSharper подсвечивает такую конструкцию, но он, опять же, подсвечивает ее и для замыканий, которые не живут за пределами итерации цикла, то есть которые полностью валидны сами по себе.
Спасибо автору. На мой взгляд код какой-то синтетический. Я правильно понимаю, что если в первом примере в классе А убрать private, т.е. строчку public Mutable Mutable { get; private set; } заменить на public Mutable Mutable { get; set; }, то «ошибка» исчезнет? Во втором примере использовано public field, что уже как бэ намекает)
Думаю, что подобные казусы могут появиться либо у совсем неопытных разработчиков, либо в результате глубоких оптимизаций в 4:30 утра, а если идти по пути наименьшего сопростивления, то и проблем вообще не возникнет.
Нет, в первом случае ошибка не исчезнет, поскольку дело не в сеттере, а в геттере.

Во втором примере может быть приватное поле, которое используется внутри этого же класса и поведение будет аналогичным. У меня там, вроде, даже фраза есть, объясняющая наличие открытого поля лишь простой демонстрации примера.
Sign up to leave a comment.

Articles