Pull to refresh

Comments 117

Казалось бы в последнем примере все логично: метод не виртуальный и поэтому реализация выбирается в момент компиляции. Мы же не просим от такого кода:
class A {}
class B: A {}

void method(A a) { System.Console.WriteLine(«A»); }
void method(B b) { System.Console.WriteLine(«B»); }

A b = new B();
method(b);

вывода на экран «B». Тут абсолютно все также и логично.
Там интересная деталь в том, что вызов метода через преобразование к классу А выдает правильный вариант, а через преобразование к классу А и затем к интерфейсу — вариант, как будто метод изначально был виртуальным.
Все правильно: все методы интерфейса — по сути виртуальные (хотя и называются немного по-другому)
Я в глубине души догадываюсь, что вся проблема в том, что вызовы через интерфейс идут в динамике («типа виртуальные»), а через преобразование — компилятор видит невиртуальный метод и вставляет прямой вызов в статике. И вся проблема именно в этом.
Могу переделать ваш пример без интерфейсов:

class A {
  public Action FooAct { get; protected set; }
  public void Foo() { Console.WriteLine("A"); }

  public A() { FooAct = Foo; }
}

class B : A {
  public new void Foo() { Console.WriteLine("B"); }

  public B() { FooAct = Foo; }
}

...

var a = new A();
var b = new B();

a.Foo(); //A
a.FooAct(); //A
b.Foo(); //B
b.FooAct(); //B
// А теперь самое интересное:
a = b;
a.Foo(); //A
a.FooAct(); //B :)


Я думаю, в данном случае каждый поймет, что именно произошло. Но почему-то, стоит заменить делегат интерфейсом — возникают сомнения или непонимание.
Как говорится, «дело не в тебе — дело во мне».
так может быть не надо копиться в глубине души и просто не начать читать подходящую литературу?

>вызовы через интерфейс идут в динамике («типа виртуальные»)
не типа, а для диспетчеризации вызовов методов интерфейса в CLR существует отдельный Vtable Map (IVMap).

в то время как для экземпляров объектов через Method Descriptor (MethodDesc), инкапсулирующий в себе текущую реализацию метода.

если разбирать примеры по виртуальным примерам, то
warning CS0108
new FooA().Foo(); 				// A
new FooB().Foo();				// B
((IFoo)new FooA()).Foo();			// A
((IFoo)new FooB()).Foo();			// B
((FooA)new FooB()).Foo();			// A
((IFoo)(FooA)new FooB()).Foo();	// B


в последней строке ((IFoo)(FooA)new FooB()).Foo(); лишний кастинг к FooA (FooB уже является имплементацией IFoo).

вообще если метод не помечен как virtual и «переопределяют», то это плохой тон.
то же самое как примера с интерфейсом, так и базовым классом.
Я знаком с «подходящей литературой», но я не воспринимаю Стандарт как нечто Б-годанное. В нем достаточно нелогичностей и странностей.
можете привести примеры? если это implementation details, то это известная тема.
лично для меня до CLR 2.0 было странным использование слабой модели памяти.
Зачем?

Для того, чтобы кто-то из нас «победил»?

Избавьте меня от этого глупого времяпрепровождения.
весьма странное поведение с Вашей стороны, т.к. сами же пишете о стандарте как:
>В нем достаточно нелогичностей и странностей.
и при вопросе: «можно ли аргументировать» — отказываетесь.
однако дело Ваше :)
Можно подумать, что если я что-то написал — я обязан это аргументировать.

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

Если у меня будет настроение — я подискутирую с вами на эту тему. Но поймите меня правильно — если я бы я хотел описать нелогичности стандарта, я бы создал топик на эту тему.

То, что я описываю, к этому не имеет никакого отношения. Это просто своего рода пятиминутка юмора, к которому почему-то отнеслись слишком серьезно.
Вопрос.
Вот ты вы даете пример:
Но как только мы хотим go some math, компилятор сразу появляется из небытия и дает нам по рукам метровой линейкой. Не сметь, мол, захламлять мне код уравнениями!

int i = j = 0;


А теперь внимание — вы ожидали, что эта строчка скомпилируется без объявления переменной j?

int i = int j = 0;

Про это я вообще молчу.

Простите, вы из каких плюсов приехали? Марсианских?
Ну человек же однозначно понимает данную конструкцию, значит ее можно формализовать, значит можно научить ей компилятор. Я думаю, автор хотел сказать это.
UFO just landed and posted this here
Кстати, функциональный C# существует и называется Nemerle.

Там многие из моих «хотелок» есть.
Немерле — это в первую очередь мета-C#. Функциональщина там через это.
Why so serious? Я же написал — что это даже не «хотелки» — это просто попытка написать те вещи, которые мы привыкли никогда не писать (потому что они не компилируются), опираясь на «логику».
Лично я не вижу «логики» в этом:

int i = int j = 0;

Ни в C++, ни в других известных мне языках объявление переменной не может быть использование как выражение (rvalue). Какой результат оно должно выдавать, где логика? А вот операция присваивания в С++ может быть rvalue, что и позволяет делать фокусы типа i=j=0 или даже

while((res=next_result())!=false)

и даже это не везде — в том же Delphi операция присваивания не может быть использована как rvalue, то есть нельзя написать а:=b:=2;
=>Какой результат оно должно выдавать, где логика?
Если честно я вообще нигде логики нашел.
Из того, что я знаю, между C# и С++ есть такие отличия, навскидку
1)Конструктор базового класса всегда вызывается в С++, а в С# ему надо указывать, иначе не вызовется.
2)protected в С# позволяет вызвать методы базового класса только на this и не позволяет вызвать их на другом объекте того же класса, в C++ это можно.
3)Более строгая типизация, нельзя int использовать как bool, например, но это уже более очевидно.
При этом у меня почему то не возникало мыслей, о том, что человеческий разум оказывается понять поведение языка C#, когда в плюсах я делал вот так а теперь не работает. Хотя местами, если не сказать большинством, эти «хочу странного», как правильно сказали, и на плюсах не имеет смысла и не будет компилиться.

ЗЫ и вообще, все эти «хочу странного» приводят к нечитаемому и неподдерживаемому коду, когда натыкаешься, хочется автора казнить ректально и прилюдно.
Немного уточнений:

1)Конструктор базового класса всегда вызывается в С++, а в С# ему надо указывать, иначе не вызовется.

Неверно.

2)protected в С# позволяет вызвать методы базового класса только на this и не позволяет вызвать их на другом объекте того же класса, в C++ это можно.

Кто вам это сказал?

3)Более строгая типизация, нельзя int использовать как bool, например, но это уже более очевидно.

Это — единственный образец «более строгой типизации». Зато в C# любое примитивное значение можно забоксить в object, чего нельзя сделать в C++ за отсутствием аналога object.
Упс, извиняюсь за пункт 2 — я прочитал немного не то, что вы написали.
Неверно.

Перепутал с джавой, видимо.
Это — единственный образец «более строгой типизации».

Отнюдь, int нельзя присвоить в short, например, возникает ошибка предлагающая принудительный каст. Хотя «расширение» происходит автоматически.
Но это еще ладно, а вот bool меня шокировал до глубины души.
По поводу строки:
int i = int j = 0;

Вы немного перемудрили:
1) Я думаю надо посмотреть описание языка и будет все ясно что не так. Любой оператор это: for, (do)while, using, try, foreach, if, либо [имя_типа имя_переменной = ] выражение[, [имя_типа имя_переменной = ] выражение]...;
2) В случаях с интерфейсами и классами тоже на самом деле все очевидно. Разница лишь в том, что у интерфейса нет реализованного метода, а у класса, от которого Вы наследуете — есть. Этим все и объясняется.
*извиняюсь вот так должно быть [имя_типа имя_переменной = ] выражение[, [имя_переменной = ] выражение]...;
Я знаю описание языка. И знаю, почему так писать нельзя. Речь не о том, что нельзя, и не о том, почему нельзя — а о том, что вроде бы «логично», но нельзя.
Компания микрософт основана инопланетянами для того, чтобы человечество было готово к тому, что во вселенной существует инопланетный разум. Почему экзопсихологи не изучают .net и PHP для меня загадка :)
Я ошибся в слове экзопсихология, по иннерции. Оно на самом деле эзопсихология.
Да и в слове «инерция» тоже.
Я вас правильно понял:)
Имел (имею?) дело с эзотерикой в программировании.
Эзотерика и эзопсихология совсем разные понятия. Нет, не правильно вы меня поняли, но я рад, что вам тоже не скучно :)
Полагаю, что многие пришедшие в славный мир .NET из славного мира С++ прекрасно помнят, как им приходилось буквально впиваться в стандарт, чтобы разобраться, почему язык ведет себя именно так, а не иначе

Какие-то странное утверждение. C# — гораздо проще и прямолинейней в сравнении с C++. Даже компилятор вам прямо указывает на места, которые выглядят «подозрительно», чего ещё для счастья надо-то?
Мне — ничего. Это пятиминутка юмора.
Что за, простите, неведомая чушь?

Уважаемый автор, вы серьезно верите в то, что пишите?
У меня просто непрерывный WTF мозга от прочитанного:

1.
int i = int j = 0;

Серьезно? Объявление переменной сразу в месте использования? А инициализация переменной?
Не надо оправдываться тем, что, мол, вы там выражение с операторами сравнения делаете. Создавать отдельные выражения-мульти-объявления-с-мульти-инициализацией — бредовый синтаксический сахар, создающий только больше путаницы в синтаксис (иногда присваивание — это оператор, а иногда — спец-синтаксис для спец-выражений).

2.
if((int i = 5) < 10)


Опять та же самая чушь — одновременная инициализация и использование переменной.
(Кстати, а вы вообще знаете чем отличаются объявление и инициализация переменных?)

Для «for» есть специальный синтаксис, который делит инициализацию некоторых переменных от их использования, т.к. это упрощает запись итерирования.
А нафига нам нужно что-то объявлять и инициализировать внутри «if»? Чтобы сразу провести сравнение? Или чтобы использовать в теле «if»'а?
Получите дополнительные очки, если догадаетесь, почему это никому не нужно.

3.
 public override void SomeMethod(int data) : base (data) { }

А вы вообще знаете, что такое «виртуальная функция»? И почему ваша идея из-за этого превращается в бред, т.к. функция уже становится не виртуальной а… черт я даже не знаю как это назвать.
Кроме того, если приложить минимальные умственные усилия, то можно понять, что идея сама по себе не имеет смысла: если в предке виртуальная функция имеет реализацию, то значит возвращает какое-то значение. Простыми словами: она сделает «return value» еще до того, как управление вообще сможет перейти к методу потомка.
Или же предлагается «сначала метод предка сделает ЧТО-ТО, вернет значение, а потом и метод потомка сделает ЧТО-ТО ЕЩЕ, используя те же самые входные параметры и возвращенное методом предка значение»?
Ну, счастливой отладки вам тогда…

Дальше уже перестал читать, т.к. реально не было сил.

Резюмирую: автор, рекомендую лучше изучить мат. часть, прежде чем пытаться делать подобные статьи, утверждая, что это все «логично». Очень надеюсь, что вы просто пошутили.
ааа, о втором пункте всю жизнь мечтал
представляете, можно было бы написать такое
var dic = new Dictionary<string, string>();
...
if (var item = dic.TryGetValueOrDefault("Key") != null)
{
   Console.WriteLine(item);
}


куча плюсов такого подхода
1. можно использовать var (сейчас холивар будет);
2. не засоряется область лишними переменными (мне не нужен доступ к переменной item в не цикла, так как она будет равна null).
3. одна строка — вместо двух/трех.

эх, в F# так можно написать и даже больше

// xml предварительно проверили на валидность через xsd.
if (let node = root.SelectSingleNode("node")
      node.Value == "value1" or node.Value == "value2")
{
   ...
}
представляете, можно было бы написать такое

Никогда не задумывались о том, почему TryGetValue имеет сигнатуру bool… out T, а не просто возвращает null, если значение не найдено?

А вообще, то, что вы хотите, в один прием делается с уже навязшей в зубах монадой MayBe:

dic.TryGetValueOrDefault("Key").Do(item => Console.WriteLine(item));
TryGetValue — не решает проблему лишней переменной вне условия :)
Монады круто, но в C# синтаксис не очень для них подходит (устанете копаться в закрывающихся скобочках).
Полагаю lair намекает, что null — корректное значение, которое может храниться в dic.
Если разрешать null, то всё равно придется либо получать значение два раза, либо использовать дополнительную внешнюю переменную.

if (dic[«key»] != null)
{
Console.WriteLine(dic[«key»]);
}

либо

var item = dic[«key»];
if (item != null)
{
}

в монадами хорошо, но кроме фигурных скобок появляются ещё и круглые, а если использовать вложенность, то совсем плохо смотрится.
Уточню:

В исходном примере вы делаете следующую проверку: «элемент есть и он не равен null», хотя гораздо чаще требуется проверка «элемент есть». И при помощи TryGetValueOrDefault эту проверку не осуществить.

Выходы: монады, туплы и out-параметр.
Для монад требуется более полная поддержка в языке.
Туплы без сопоставленя с образцом крайне неудобны.
Так что выбора нет.

А скобочки — в if они тоже есть.
устанете копаться в закрывающихся скобочках

К счастью, в IDE для решения этой проблемы достаточно средств.
Вспоминая обсуждение в недавней статье про монады, ваш пример со сравнение с нулем можно запсиать так:

dic.TryGetValueOrDefault("Key").With(Console.WriteLine);


Добавив собственный метод:

public static class TWith
{
	public static void With<T>(this T t, Action<T> a) where T : class
	{
		if(t != null)
		{
			a(t);
		}
	}
}


Да и все остальные проверки сдалеть не сложно, добавив таким-же образом немного LINQ-подобного синтаксиса, аналогичного Where.
Так и запишем: не обновляет комментарии, прежде чем оставлять свой.
Второй пункт так, как вы его описали у себя, возможен и в С++.
Зачем сразу вводить новый метод?

Если уж мечтать, то сразу так:
if (dic.TryGetValue("Key", out var item))
{
    Console.WriteLine(item);
}
Кстати, не менее смешной вариант, чем мой.

Привет из 2018го года. В седьмой версии C# разрешена конструкция out var item :-)

и? здесь как rvalue переменная используется, а не объявление.

#include int main()
{
int i = -1;
if (int j = i > 0)
{
std::cout << «ok!»;
}
else
{
std::cout << «not ok!»;
}
}

Все логично.
А, все понял, извините.
Я просто пошутил. Я прямо написал об этом в первых абзацах еще до хабраката.

Ни одни вменяемый человек не будет возмущаться тому, что код, который помечен как нерабочий, не работает.

Там разве что объявление переменной в блоке if — полезная вещь.
Дык в том то и проблема, что шутка получилась слишком глупой: ваши примеры, которые «нам понятны и логичны», на самом деле совершенно нам не понятны и не логичны.
Получился этакий «сортирный юмор» IT-сферы.
А нафига нам нужно что-то объявлять и инициализировать внутри «if»? Чтобы сразу провести сравнение? Или чтобы использовать в теле «if»'а?
Google Go так умеет и это очень здорово.
К статье ещё можно пункт добавить: «Почему мне надо переменную объявлять вне цикла do-while, чтобы использовать её в проверке выхода»

Почему нельзя написать так:
do
{
bool repeat = // расчет в несколько строк
}
while (repeat);

Вне цикла от переменной нет никакого толку. Ну и соответственно почему нельзя объявить переменну в цикле while-do, а в for можно.

while (bool repeat = true == true) // либо while (bool repeat = true; repeat)
{
  ... 
}
А зачем городить второй for?
for(bool repeat = true; repeat;)
{
  ...
}
Честно говоря, мне пришлось в голове разложить цикл на компоненты, чтобы понять, что именно тут написано.
Так эта запись не отличается от записи с while выше, и несколько проще для понимания, чем большинство конструкций ФЯП. И проще даже чем int i = int j = 0
Ну «вон из профессии» тогда. for это не конструкция вида «переменной присвоить ноль; сравнить меньше ли переменная N; прибавить один», а вовсе даже «что-то проинициализировать; проверить значение; сделать что-то после итерации цикла».
А что вы хотели сказать своим while?

while ((repeat = true) == true) // while (true)
{
  ...
}

while (repeat = (true == true)) // while (true)
{
  ...
}


Может, поэтому в C# (и аналогичных языках) и нет такой конструкции?
Самое интересное то, где объявлена repeat. У Вас она за циклом. А зачем она нужна вне цикла?
Не спорю. Но внесение объявления и инициализации в while, как мне кажется, несет больше проблем, чем пользы. В том числе и из-за подобных неочевидностей (например, придется оговаривать, что первый знак "=" после объявления — это не присваивание, а инициализация переменной, выполняемая лишь однажды при входе в цикл).
Там иной синтаксис: инициализация, условие и итератор явно разделяются точкой с запятой. Ну а на отсутствие необходимости изобретать еще один for уже указывали выше.
Предусловие для For? На сколько я понимаю в For условие проверяется после выполнения тела цикла :)
???

Вообще-то перед первым входом тоже, иначе бы для пустых массивов были бы очень смешные побочные эффекты.
Тогда вам дорога к Nemerle: в нем, если не ошибаюсь, есть макросы, позволяющие расширять язык :)
Мое личное мнение: не стоит чрезмерно раздувать синтаксис, вводя новые конструкции и налагая различные ограничения / соглашения. Повторюсь, IMHO.
Ну это же так, рассуждения в слух :) О том, что иногда кажется, что это «должно работать именно так».
Ещё есть «неадекватность» по моему мнению в операторе ??

var name = root.Element(«Employee»).Element(«PersonInfo»).Attribute(«Name»).Value ?? "";

я же (вроде как), четко написал компилятору, что меня интересует значение левой части, если оно не пустое, иначе права часть.

Реализовать такое тоже думаю не сложно, объявив набор стандартных исключений, которые глотаются оператором ?? (например, KeyNotFound, NullRefenecesException и т. п.)
Вот тут точно нет никакой неадекватности. Компилятор не может за вас знать, вы хотите значение левой части, если дерево валидно, или в любом случае. Так что, следуя принципу fail early, среда выполнения избавляет вас от трудноотлавливаемых ошибок.

И да, это тоже место для монады MayBe.
Да, согласен. Но чтобы написать правильно тот кусок (с проверками на null), необходимо затратить значительное количество строчек, при написании которых, можно будет отстрелить себе не только ногу, но и голову :)
Ну и возьмите себе монады, где стрелять в ноги сложнее.
Всяко проще, чем вложенные проверки на null.

Dense code.
Согласен. Но на самом деле функциональщина нужна далеко не везде. Например, в моём примере проще сделать так.

static class Ext
{
    public static XElement Element(this XElement el, string name)
    {
         if (el == null) return null;
         return el.Element(name);
    }
}
Во-первых, это та же самая функциональщина, ничем не отличается. Просто у вас частный случай, а в With/Do — общий.
Методы расширения — это функциональщина? Возможно, но писать значительно проще.

   var name = root.Element(«Employee»).Element(«PersonInfo»).Attribute(«Name»).Value() ?? "";


   var name = root.Element("Employee").With(e=>e.Element("PersonInfo").With(e=>e.Element("Name")).With(e=>e.Attribute("Name")).With(e=>e.Value) ?? "";


а я линивый :) и ошибиться боюсь.
Нет, пайплайнинг функций — это функциональщика.

Возможно, но писать значительно проще.

Ну, я не вижу особой разницы с тем, что ниже, зато решение универсальное.

root.With(r => r.Element("Employee").Element("PersonInfo").Attribute("Name").Value()) ?? ""
Что будет если тег Employee не будет найден?
Если любое звено в цепочке вернет null, будет возвращен null, дальше будет coalesce к пустой строке.
Это как так? r.Element(«Employee») возвращает null и вызов Element(«PersonInfo») у null'а, дает NullReferencesException. Или тут магия?
Насколько я понял, эта магия называется Expression
А кто вам сказал, что то, что вы видите после => — это исполняемый код? Это Expression, его интерпретация полностью на усмотрение метода.
Вам так часто (и совершенно обосновано) стали рекомендовать монады, что может уже пора попробовать ФП-язык?
Обожаю F#. Но мы ведь тут обсуждаем, что бы ещё такого можно было бы накрутить на C# :)
Я себе вообще не представляю как можно такие языковые конструкции использовать в сложных коммерческих проектах. Предпочитаю разделять какое-то действие на несколько строк в несколько символов, чтобы вернувшись через год к этому куску не вспоминать, что же тут все-таки происходит.
Ваши примеры разве что для олимпиадных задач «уместить алгоритм какой-то там в 100 байт исходников»
Статья о том, как с# многократно и с особым цинизмом не давал автору выстрелить себе в ногу.
Пример с WAT совсем не в тему, т.к. автор допускает в видео несколько ошибок, которые новички воспринимают как неочевидности.
Дык у меня вообще весь код — одна сплошная ошибка, он даже не компилируется.
Нет нет, дело не в этом. Автор на видео говорит что складывает два объекта так: {} + {}, но он глубоко заблуждается. Он говорит что в результате мы получаем объект "[object Object]", но он неправ, так как в результате он получает строку "[object Object]". Именно поэтому видео с WAT не в тему, т.к. автор делает совсем не то что говорит.
И после этого люди удивляются, почему в школе учат паскалю…
Может разовьете свою мысль?

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

Или вы считаете, что вопросы закономерны и логичны и показывают превосходство синтаксиса паскаля над C#? Тогда приведите примеры соответствующих синтаксических конструкций в паскале.

Или же вас печалит бредовость как паскаля, так и топика и вы считаете, что озбавиться от такого пережитка как паскаль, мешает общее низкое качество преподавания, преводащее к подобным вопросам?

Из вашего комментария не ясно выше отношение ни к топику. ни к паскалю.
вот ставить break-и внутри case-ов (после default метки) вот это создает вопрос «зачем»…
Вообще обязательность break после case полностью убивает смысл этого break.
У break после case нет никакой обязательности.
A jump statement such as a break is required after each case block, including the last block whether it is a case statement or a default statement. With one exception, (unlike the C++ switch statement), C# does not support an implicit fall through from one case label to another. The one exception is if a case statement has no code.


Естественно вместо break там может стоять return, только не понятно зачем break вообще нужен, раз уж нельзя его убрать и попасть в следующий блок после завершения текущего.
Про это и речь.

Кстати, там еще может стоять goto case.
Меня больше расстраивает тот факт, что case не создает отдельной области видимости переменной.
Например,
switch (...) 
{
  case ...:
    int x = 5;
    break;

  case ...:
    int x = 6; //Ошибка
    break;
}


Во втором блоке case видима переменная x из первого блока, что и приводит к ошибке. Лично я еще ни разу не сталкивался с необходимостью иметь доступ к переменным из другого блока, а вот с ситуациями наподобие приведенной выше сталкиваться приходилось.
Например,
switch (...) 
{
  case ...:
  {
    int x = 5;
    break;
  }

  case ...:
  {
    int x = 6; // Нет ошибки
    break;
  }
}
К сожалению, ваш красивый трюк студия автоматически превратит вот в это:
switch (...) 
{
  case ...:
    {
      int x = 5;
      break;
    }

  case ...:
    {
      int x = 6; // Нет ошибки
      break;
    }
}
Эмм… Не вижу в этом особой проблемы. К тому же, форматирование кода — это фича студии, не имеющая отношения к языку. Я лишь хотел показать, что при необходимости вполне возможно создать в case локальную область видимости (хотя сам предпочитаю выносить объявление переменной за пределы switch).
Да, но подобное создание области видимости несколько неудобно, вот и все. Если бы case создавала свою область видимости, было бы чуть удобнее.
Согласен. Думаю, авторы языка исходили из двух предпосылок:
1. Единообразие: область видимости ограничена {… } (с небольшим отступлением для цикла for).
2. Преемственность: такое поведение case уже привычно для программистов на C / Java (а легкость перехода на новый язык была одним из приоритетов при создании C#). Последствия нарушения преемственности хорошо видны на запрете «проваливания» между case'ами: возмущенные крики слышны до сих пор, хотя языку уже около 10 лет.
Бред.
Похоже на результат портирования джуниора работающего со слаботипизироваными языками, на .NET

Смысла показывать, что компилятор .NET не такой уж и групый, а очень даже умный по сравнению с другими, которые в консоль бросают слабо понятные сообщения.

В .NET есть куда более не тривиальные вещи, и поведения.
Если уж задумались, то подумайте, как в .NET можно создать экземпляр _интерфейса_
(это возможно сделать, при некоторых условиях)
Вы уже не можете отличить бред от юмора?
Если не ошибаюсь, у вас ошибка в примере с юзингом. В том смысле, что в трай-кэтч не попадает сама инициализация переменной.
Попадает. В противном случае была бы утечка неуправляемых ресурсов в случае прерывания потока сразу после присвоения переменной до начала блока try.

В .NET 3.5, впрочем, именно так и происходило.
Очень интересно, видел как раз вопрос на собеседовании на эту тему пару лет назад. А не покажите источник с примером? А то мсдн как-то либо не обновился, либо вы все-таки путаете msdn.microsoft.com/en-us/library/yh598w02.aspx
Я за МСДН-ом не особо слежу, я у Липпета в блоге читал, что он знает про проблему (в контексте обсуждения 3.5) и они думают, как ее решать. В качестве одного из вариантов рассматривался и такой. Чем все закончилось — не знаю, но мне почему-то казалось, что они возьмут именно его как наиболее логичный.
А кто мешает прервать поток непосредственно перед присваиванием? Подобное передвижение не исправляет ошибки.

Единственной способ предотвратить утечку неуправляемых ресурсов в условиях Thread.Abort() — SafeHandle
Прервать управляемый поток можно только в том месте где это допускается CLR, а не в любом. Например, в случае CER прерывания потока в регионе быть не может в принципе.

Так что немного подкрутив CLR, можно было получить требуемое поведение.
Его и получили — при помощи SafeHandle.
Насчет «проглатываний» объявлений переменных — да и gcc так делает с выключенной оптимизацией. Насчет последнего «волшебного» примера — я думаю все C#-разработчики с хотя бы небольшим опытом за плечами спокойно читают такой код и понимают почему такие результаты с ходу. Пытаться показать себя умнее авторов языка — странно.

На мой взгляд в C++ куча «нелогичных» и «неправильных» вещей, потому что я изо дня в день вижу C# код и он мне логичен и понятен, а «странные конструкции» и «а почему нельзя вот так» в C++ видятся. Писал бы постоянно на нем, начал бы подумывать так о C#.

А еще непонятно, зачем там хаб «ненормальное программирование».
Sign up to leave a comment.

Articles