Comments 86
Кому нужно больше задачек, есть Задачник.NET.
Помню, что, когда меня интервьюировали после университета, то тоже спрашивали такие вопросы. На самом деле вопрос был про виртуальные методы и оператор new, которого тут нет. У меня был простой ответный вопрос «А что, вы так пишите?».
Как ни странно, но goto в C# все еще поддерживается. Хотя не особо нужен.
Вообще-то нужен. Конструкция такого вида недопустима в C# (согласно требованиям C# конец разделов switch должен быть недостижим)
switch(variable)
{
case 1:
Console.WriteLine("case 1");
//need break, goto case, return or throw.
case 2:
Console.WriteLine("case 2");
break;
default:
Console.WriteLine("default");
break;
}
В MSDN предлагают использовать оператор goto в таких случаях. В итоге выходит такой код:
switch(variable)
{
case 1:
Console.WriteLine("case 1");
goto case 2;
case 2:
Console.WriteLine("case 2");
break;
default:
Console.WriteLine("default");
break;
}
Такого рода конструкции не зря недопустимы в языке. Потому что, модифицируя код для case 2 вы на самом деле модифицируете его и для другого случая, т.е. код вводит в заблуждение. Возможно, это когда-то очень нужно (честно говоря, не представляю случая, когда такая необходимость была бы неохбодима), но её следует всеми силами избегать. У Макконнела конец 15-й главы посвящён этому вопросу.
P.S. А второй пример в MSDN вообще ужасен. Вместо того, чтобы выделить цикл в отдельный метод и делать из него return, используют goto в место, которое даже не находится сразу после цикла.
P.S. А второй пример в MSDN вообще ужасен. Вместо того, чтобы выделить цикл в отдельный метод и делать из него return, используют goto в место, которое даже не находится сразу после цикла.
не представляю случая, когда такая необходимость была бы неохбодима
Очень просто: когда один кейс представляет собой расширенный вариант другого. В этом случае можно сначала сделать дополнительную часть, а потом перейти к общей.
Под «необходима» я имел ввиду буквальное «нельзя обойтись без неё». Этот вариант очевиден, более того, он напрашивается. Но опять-таки, то, что очевидно в момент написания будет неочевидным при поддержке.
не представляю случая, когда такая необходимость была бы неохбодима
Типичный пример, когда это необходимо — конечный автомат. Наши кейсы в switch структуре будут состояниями, между которыми бы будем перескакивать в соответствии с логикой работы. Другое дело, что без goto можно и в этом случае обойтись — достаточно завернуть switch в while цикл, и устанавливать переменную для следующей итерации. Таким образом, goto case 2: превратится грубо говоря в variable = 2; break; и на следующей итерации мы туда и попадём. Я вот тоже не сторонник goto, этот оператор годится для несложных и «очевидных» случаев, а при разрастании кода обилие goto превратит код в спагетти, поэтому в сложных ситуациях лучше обойтись более продвинутыми паттернами (иногда используют итераторы, либо System.Activities.Statements.StateMachine, и т.д.).
Это для вот таких случаев. Выделять здесь цикл в отдельный метод нельзя — его вызов будет некорректен в любом случае.
try{
myEvent1.Reset();
myEvent2.WaitOne();
for (;;){
for (;;){ goto labelExit;
}
labelExit:;
}
}
finally{
myEvent1.Set();
}
А почему нельзя так?
switch(variable)
{
case 1:
case 2:
Console.WrileLine("case 1");
Console.WrileLine("case 2");
break;
default:
Console.WrileLine("default");
break;
}
Объясните джависту:
2,3. Почему так? У нас наоборот.
7. А почему это вообще работает? Ведь в момент вызова делегата i уже не существует, мы ведь вышли из ее области видимости.
2,3. Почему так? У нас наоборот.
7. А почему это вообще работает? Ведь в момент вызова делегата i уже не существует, мы ведь вышли из ее области видимости.
7. Тут важно разрушается ли переменная
i
в конце итерации, или переиспользуется. В данном случае переиспользуется. Чтобы исправить ситуацию достаточно либо завести новую переменную внутри цикла, либо использовать foreach (int i in Enumerable.Range(0, 10))
начиная с C# 4.Я о другом. Почему [10, ..., 10], а не [0, ..., 9] — это понятно. Непонятно почему оно в текущем виде компилируется и работает, а не говорит что-то в духе «в момент вызова делегата переменной i уже не будет существовать, так что иди нахрен».
Другими словами, почему последнее i=10 сохраняется, а не разрушается.
Другими словами, почему последнее i=10 сохраняется, а не разрушается.
Ну потому что переменная
Но любой нормальный статический анализатор подскажет, что здесь нечисто.
i
в таком случае будет находиться в куче, в инстансе специалного класса замыкания, и будет уничтожена только после GC.Но любой нормальный статический анализатор подскажет, что здесь нечисто.
Вот я и негодую что язык такое позволят. Ибо очевидно что что-то не так.
Наверное на то есть причина. Потому как у соседнего тут Go точно такая же «фича» — замыкание захватывает ссылку, GC доволен и получается тот же самый результат. Тоже фигурирует в такого рода списках, что нужно просто знать, чтобы потом не было неприятно.
А какие ещё опции? Как по-другому такой код написать?
Func<int> F(int i) { return () => i; }
Можете пояснить, что вы имеете ввиду? Неясный синтаксис.
Я так понимаю, вы имеете ввиду что-то вроде:
Func F = () => i;
Но это, по сути, то же самое, что и с делегатом выше.
Я так понимаю, вы имеете ввиду что-то вроде:
Func F = () => i;
Но это, по сути, то же самое, что и с делегатом выше.
Есть мысль, что захватывать можно только константы.
Есть мысль, что захватывать можно только константы.
А смысл тогда их захватывать?
Идеологически оно может и верно, но практическая польза от замыканий при этом падает.
Ок, признаю, я действительно очень неудачно выбрал термин. Имел ввиду не static final из java или const из c++, а просто что-то неизменяемое в данной области видимости. Т.е. очевидно, что в вашем примере i — это переменная, и может принимать разные значения, но внутри метода F меняться значение не может. Так что мы можем создать замыкание и утащить туда конкретное значение.
Возвращаясь к исходному примеру. Я бы понял, если бы на каждой итерации это были бы разные неизменяемые i. В таком случае, правильным был бы вывод [0, ..., 9]. Но вот в текущей реализации, я считаю, что создавать замыкание i (хз как правильно, «над i», «с i») нельзя. И следить за этим должен компилятор.
Возвращаясь к исходному примеру. Я бы понял, если бы на каждой итерации это были бы разные неизменяемые i. В таком случае, правильным был бы вывод [0, ..., 9]. Но вот в текущей реализации, я считаю, что создавать замыкание i (хз как правильно, «над i», «с i») нельзя. И следить за этим должен компилятор.
Слишком много магии, определение изменяемости/неизмениемости переменных. Предложение об изменении типа переменной в зависимости от скоупа
Кстати, язык С++ даёт вам контроль над этим моментом, позволяя захват как по ссылке (как в нормальных языках), так и по значению (копируя данные).
if (a is B) { /* переменная a типа B */ }
по этой же самой причине отклонили. Нельзя так просто взять и узнать, модифицируется ли переменная в скоупе, или нет. Вместо этого будет использоваться синтаксис if (a is B b)
, создающий новую переменную, что и рекомендуется делать, если вы запутались с замыканиями.Кстати, язык С++ даёт вам контроль над этим моментом, позволяя захват как по ссылке (как в нормальных языках), так и по значению (копируя данные).
1. Какой магии? Если компилятор может доказать что присваивание ровно одно — значит неизменяемая. Во всех остальных случаях считаем изменяемой. Например понятие «effectively final» из Java8: «A variable or parameter whose value is never changed after it is initialized is effectively final.». Еще ни разу не сталкивался с багами в этом месте. Естественно, я не говорю про C++, но в C#, на сколько я знаю, прямой работы с памятью нет, так что отследить изменения всегда должно быть возможно.
2. Kotlin, Smart Casts.
2. Kotlin, Smart Casts.
оффтоп
Дурацкие мемы, автоматически в голове этот текст рисуется на соответствующем фоне.
Нельзя так просто взять и узнать
Дурацкие мемы, автоматически в голове этот текст рисуется на соответствующем фоне.
Если компилятор может доказать что присваивание ровно одно — значит неизменяемая. Во всех остальных случаях считаем изменяемой.Если бы вы открыли статью, на которую я дал ссылку, вы бы увидели там объяснение, почему в C# это не так.
Вкрадце, локальная переменная может находиться на куче, и там её значение может быть изменено по огромному количеству причин. (Обратите внимание на термины «переменная» и «значение» здесь — не путать с значимыми типами, они здесь роли не играют.)
Вы мне не ту часть скинули. В этой написано лишь (по данной теме) «программист и сам в состоянии уследить» (а с таким подходом недолго и до javascript докатиться). Проблемы изменяемости описаны в первой части. Ну так там и речь не про локальные переменные. По сути, про локалы остается лишь перегрузка методов из конца первой части. Там да, признаю, действительно проблема.
Вообще, вижу что в C# с этим действительно сложнее чем в Java. А в котлиновских smart casts так вообще на ряд вещей забили.
Вообще, вижу что в C# с этим действительно сложнее чем в Java. А в котлиновских smart casts так вообще на ряд вещей забили.
внутри метода F меняться значение не может
Вот только возвращенное замыкание существует и после того, как метод F перестал существовать. И если с
int
все просто, он был скопирован на входе в метод и с тех пор неизменен, то с reference type все намного сложнее. В реальности, правильная формулировка звучит как «замыкаться надо на immutable», как оно чаще всего в функциональных языках и делается, но в C# этого достичь не так-то просто.
А в референсах скопировали саму ссылку и окей)).
На самом деле, я прекрасно понимаю что при изменением внутреннего состояния объекта по скопированной ссылки совершенно спокойно могут быть (и будут) проблемы, описанные в примере из статьи. Так что в самом правильном варианте — только immutable. Просто если введением local final variable можно убрать если не всю проблему, но хотя бы ее часть, то почему-бы этого не сделать?
А вообще, для данной задачи понятие владения из Rust подходит даже больше чем immutable.
На самом деле, я прекрасно понимаю что при изменением внутреннего состояния объекта по скопированной ссылки совершенно спокойно могут быть (и будут) проблемы, описанные в примере из статьи. Так что в самом правильном варианте — только immutable. Просто если введением local final variable можно убрать если не всю проблему, но хотя бы ее часть, то почему-бы этого не сделать?
А вообще, для данной задачи понятие владения из Rust подходит даже больше чем immutable.
Вполне все логично и обяснимо.
Захват происходит по ссылке. Очевидно что такая переменная выделятся уже не из стека (что характерно для структур), а из кучи и хранится там пока есть хотябы одна ссылка на эту переменную (в данном случае ссылка храниться в делегатах).
Вообще нужно аккуратнее со всякими ref и замыканиями работать
Захват происходит по ссылке. Очевидно что такая переменная выделятся уже не из стека (что характерно для структур), а из кучи и хранится там пока есть хотябы одна ссылка на эту переменную (в данном случае ссылка храниться в делегатах).
Вообще нужно аккуратнее со всякими ref и замыканиями работать
В примерах 2 и 3 в C# так хотя бы потому, что эти методы не виртуальные по умолчанию как в Java. К слову, если попытаться сделать виртуальными, будет ошибка вроде «main.cs(22,30): error CS0115: `Program.B.abc(Program.P)' is marked as an override but no suitable method found to override». C++ себя ведёт так же.
Вопрос в том, почему Java так странно работает, если сделать аннотацию @ Override, то будет ошибка компиляции как в C# (error: method does not override or implement a method from a supertype), иными слова метод наследника не переопределялся, но при этом Java лезет вверх по наследованию для подходящего метода. Чтобы результат был как в C# (abc из B) надо метод базового класса сделать private.
Самое весёлое, что такой код из задачи никто и никогда не стал бы писать.
Вопрос в том, почему Java так странно работает, если сделать аннотацию @ Override, то будет ошибка компиляции как в C# (error: method does not override or implement a method from a supertype), иными слова метод наследника не переопределялся, но при этом Java лезет вверх по наследованию для подходящего метода. Чтобы результат был как в C# (abc из B) надо метод базового класса сделать private.
Самое весёлое, что такой код из задачи никто и никогда не стал бы писать.
Не соглашусь, в java как раз все логично (по крайней мере, в данном случае). Метод определяется не только по имени, но и по типам параметров. Соответственно abc(int) и abc(double) — это разные методы. Потому и не переопределяется. Вот если в наследнике тоже сделать abc(int), то будет честное переопределение и напечатается B.
Ну тогда он на результат вообще никак влиять не будет. Независимо от класса B. Так что явно плохое исправление.
Безусловно. Просто хотелось понять непонятный результат.
надо метод базового класса сделать private
Ну тогда он на результат вообще никак влиять не будет. Независимо от класса B. Так что явно плохое исправление.
Самое весёлое, что такой код из задачи никто и никогда не стал бы писать.
Безусловно. Просто хотелось понять непонятный результат.
Вообще при поиске метода, должна учитываться полная сигнатура, и в данном случае у класса B есть две публичных перегрузки, abc(int) и abc(double). Почему компилятор выбирает вторую для вызова при наличии подходящей сигнатуры abc(int), очень хороший вопрос, и стоит пожалуй с ним подробнее разобраться. Виртуальность здесь совершенно не причем.
А вот и пояснения странному поведению вопроса 2: http://csharpindepth.com/Articles/General/Overloading.aspx
по поводу номера 2
На С++ тоже так. Честно говоря, я даже и не знал о таком, ибо никогда так не напишу… но получается что при выборе метода компилятор считает что double «наследуется» от int ???
На С++ тоже так. Честно говоря, я даже и не знал о таком, ибо никогда так не напишу… но получается что при выборе метода компилятор считает что double «наследуется» от int ???
Вот еще боян :)
Выводит «Вот так вот :D»
Привести пример secretFunction.
static void Main(string[] args)
{
double x = secretFunction();
if (x != x)
{
Console.WriteLine("Вот так вот :D");
}
Console.ReadLine();
}
Выводит «Вот так вот :D»
Привести пример secretFunction.
Ответ
public static double secretFunction()
{
return 0.0/0.0;
}
Ну уж если быть точным то в 1
1) «Обе переменные не инициализированы», это ложное утверждение, поля класса инициализируются в значения по умолчанию (они же нули) при создании экземпляра, или в данном случае при создании экземпляра типа.
2) «если быть более точным, то это immutable тип, что означает reference тип с семантикой value типа», в данном контексте тоже бред, да и вообще бред, что значит с семантикой value типа? Значение локальных переменных области видимости, хранится на стеке? нет! В методы передается копия? Нет!
1) «Обе переменные не инициализированы», это ложное утверждение, поля класса инициализируются в значения по умолчанию (они же нули) при создании экземпляра, или в данном случае при создании экземпляра типа.
2) «если быть более точным, то это immutable тип, что означает reference тип с семантикой value типа», в данном контексте тоже бред, да и вообще бред, что значит с семантикой value типа? Значение локальных переменных области видимости, хранится на стеке? нет! В методы передается копия? Нет!
Хочу еще вопросов, разных, красных синих, и малиновых. Довольно занимательные задачи.
Было бы хорошо если бы было объяснение для каждого…
Было бы хорошо если бы было объяснение для каждого…
Console.ReadLine();
Зачем вы везде это пишете?
Чтобы консоль не закрывалась и можно было посмотреть результат.
> float и double не являются встроенными типами данных (integral types)
такой перевод (или его применение) коробит
float и double настолько же встроенные типы как и int
лучше integral types так и называть «интегральные типы» или целые (включая char & bool)
https://msdn.microsoft.com/ru-ru/library/ya5y69ds.aspx
P.S. интегральные + числа с плавающей точкой = арифметические
такой перевод (или его применение) коробит
float и double настолько же встроенные типы как и int
лучше integral types так и называть «интегральные типы» или целые (включая char & bool)
https://msdn.microsoft.com/ru-ru/library/ya5y69ds.aspx
P.S. интегральные + числа с плавающей точкой = арифметические
Верно. integrated != integral. Сбило с толку. Вернее даже будет сказать built-in.
msdn.microsoft.com/ru-ru/library/ya5y69ds.aspx
msdn.microsoft.com/ru-ru/library/ya5y69ds.aspx
Поразвлекаться неплохо, но если вы реально принимаете решение брать или не брать человека на основе этих вопросов, то это ппц. Особенно эта каша из классов A, B, C с наследованием.
Есть основы, которые надо знать (типа отличния абстстрактного класса и интерфейса), а есть попытки заставить кандидата компиллировать говнокод в голове.
Есть основы, которые надо знать (типа отличния абстстрактного класса и интерфейса), а есть попытки заставить кандидата компиллировать говнокод в голове.
Я в таких случаях в ответ спрашиваю правда ли такой код встречается в репозитории. И если да, то для меня это сигнал не идти сюда.
Дело не в коде, а в понимании того, как работает тот инструмент который вы используете, без понимания даже следуя лучшим практикам можно сделать такое интересное решение.
Скорее процент правильных ответов это очень косвенный признак того, сколько лет автор провёл, читая код самых разных проектов. Что, конечно, приведёт к печальному результату собеседования на Junior программиста.
Хотя… с практической точки зрения это можно использовать чтобы деморализовать претендента и заставить его подписать трудовой договор на меньшую з/п.
Хотя… с практической точки зрения это можно использовать чтобы деморализовать претендента и заставить его подписать трудовой договор на меньшую з/п.
Так и не понял, при чем тут код разных проектов? Я меня не так много проектов, которые я реально читал, почти все задачи для меня понятны. А не понятные в свою очередь интересны, вот вы например знали почему в 2 и 3 получается такой результат? Я не знал и не жалею, что потратил время, на то что бы разобраться.
Так и не понял, при чем тут код разных проектов?Чтобы слёту ответить на все вопросы, на мой взгляд, нужно иметь опыт, уже набив шишки на подобных чьих-то решениях в чужих проектах. Так как вопросы на мой взгляд не совсем тривиальные и даже сомнительные (в плане целесообразности использования), может понадобиться много времени, чтобы повстречать их ВСЕ в тех или иных реальных проектах. Почему в проектах и почему реальных? Потому что лично я не думаю, что можно заполучить соответствующие знания, лишь просто прочитав несколько книг.
Не соглашусь, вот например задачка 2 ну и 3 за одно, очень интересной оказалась и реально может устроить в реальной жизни сюрприз.
А теперь более каверзный вопрос на тему вопроса номер 7.
delegate void SomeMethod();
static void Main(string[] args)
{
List<SomeMethod> delList = new List<SomeMethod>();
foreach (int i in System.Linq.Enumerable.Range(1, 10))
{
delList.Add(delegate { Console.WriteLine(i); });
}
foreach (var del in delList)
{
del();
}
}
Ответ
Правильный ответ: какую версию C# вы используете? При C# >= 5: будут выведены числа от 1 до 10, для более старых — десять десяток.
Да, это breaking changes, можно почитать stackoverflow.com/questions/12112881/has-foreachs-use-of-variables-been-changed-in-c-sharp-5
Да, это breaking changes, можно почитать stackoverflow.com/questions/12112881/has-foreachs-use-of-variables-been-changed-in-c-sharp-5
А теперь поясните зачем нормальному человеку в 2016 пользоваться delegate, когда есть
Func<T>и прочие.
Если вне контекста задачи — то для событий, например. А в контексте — ну делегат и делегат, от замены на Action/Func концептуально ничего не поменяется.
Если честно, никогда не сталкивался с этой проблемой в реальной жизни. Зато каждый, кто прочитал про closure, норовит сунуть этот пример в интервью (обязательно с циклом for).
Я не спорю что знать полезно. Просто покажите мне человека, который столкнулся с этим в реальном проекте.
Я не спорю что знать полезно. Просто покажите мне человека, который столкнулся с этим в реальном проекте.
Я просто написал пример по образу и подобию примера в статье, так-то там лямбду надо использовать и Action.
А вот такой код выдаст 1 2 3 на любой версии языка
Ибо Linq ленив и замыкание здесь будет стряпаться прямо перед выполнением
static void Main(string[] args)
{
var delList = Enumerable.Range(1, 3)
.Select(i => (Action) delegate { Console.WriteLine(i); });
foreach (var del in delList )
{
del();
}
}
Ибо Linq ленив и замыкание здесь будет стряпаться прямо перед выполнением
Так, а я вот даже не поленился, сменил версию языка на C# 4.0, и для верности даже версию фреймворка на 3.5, но foreach в лбом варианте упорно выдает 1 2 3
Что-то тут нечисто. Сижу в 2015 студии
Что-то тут нечисто. Сижу в 2015 студии
Тут, вроде, нет замыкания. Просто i передаётся как параметр в функцию, где благополучно копируется по значению и выводится.
Есть.
Здесь нет замыкания, ну прям совсем нет.
Ну я даже не знаю, можете декомпилировать и проверить. В .NET нет инструментов, чтобы оптимизировать этот C# код с замыканием таким образом, чтобы он выполнялся без замыкания.
Где тут замыкание?
var delList = Enumerable.Range(1, 3).Select(i => (Action) delegate { Console.WriteLine(i); });
var delList = Enumerable.Range(1, 3).Select(i => (Action) delegate { Console.WriteLine(i); });
Создаётся через ключевое слово
delegate
.Да действительно я ступил, тут есть замыкание, но когда происходит захват контекста в данном случае i, мы имеем захват контекста вызова i =>… что приводит к тому, что замыкание каждый раз захватывает не внешнюю переменную, а значение i на момент вызова, т.е. по факту происходит захват значения 1, 2, 3, 4, а не захват внешней переменной.
var smth = 0;
var delList = Enumerable.Range(1, 3).Select(i => { smth = i; return (Action) delegate { Console.WriteLine(smth); }; });
var smth = 0;
var delList = Enumerable.Range(1, 3).Select(i => { smth = i; return (Action) delegate { Console.WriteLine(smth); }; });
Ну еще .ToList() надо для порядка иначе отображение совпадет с передачей значения.
Да, я конечно же говорю про вариант с .ToList(), он тоже выдает 1 2 3. И в 2013 студии, кстати тоже 1 2 3
var smth = 0;
var delList = Enumerable.Range(1, 3).Select(i => { smth = i; return (Action) delegate { Console.WriteLine(smth); }; }).ToList();
Выдает 3 3 3 т.к. происходит захват переменной smth. А ее значение к моменту вызова делегата с замыканием уже 3 и не меняется, что собственно происходит в примере приведенном в статье.
var delList = Enumerable.Range(1, 3).Select(i => { smth = i; return (Action) delegate { Console.WriteLine(smth); }; }).ToList();
Выдает 3 3 3 т.к. происходит захват переменной smth. А ее значение к моменту вызова делегата с замыканием уже 3 и не меняется, что собственно происходит в примере приведенном в статье.
Sign up to leave a comment.
Каверзные вопросы по C#