Хабрахабр

[Перевод] Исчерпывающий список различий между VB.NET и C#. Часть 1

image

NET обогнал по популярности C#. Согласно рейтингу TIOBE в 2018 году VB. NET, Энтони Грина. Совпадение или нет, но в феврале Эрик Липперт, один из создателей C#, призвал читателей обратить внимание на блог его друга, бывшего коллеги по команде компилятора Roslyn и, по совместительству, ярого фаната VB. Представляем вашему вниманию первую часть перевода статьи Энтони Грина «Исчерпывающий список различий между VB. «Подобные ресурсы — это глубинные детали от экспертов, которые не так легко найти, читая документацию», пишет Эрик. Возможно, именно в этих различиях кроется секрет динамики рейтинга этих языков.
За без малого полжизни я был свидетелем и участником бесчисленных дискуссий о том, насколько похожи или отличаются два самых популярных языка . NET и C#». Сперва как любитель, затем как профессионал и, наконец, как защитник клиентов (customer advocate), программный менеджер и дизайнер языков, я без преувеличения могу сказать, сколько раз я слышал или читал что-то типа:
NET.

NET — это на самом деле просто тонкий слой поверх IL, как C#…» «… VB.

или

NET на самом деле просто C# без точек с запятой …» «… VB.

Как если бы языки были XML-преобразованием или таблицей стилей.

Я столкнулся вот с таким небольшим различием в одном единственном месте — это баг? И если некий пылкий посетитель не пишет это в комменте, то часто это подразумевается в вопросах вроде: «Привет, Энтони! За что нам такая несправедливость?! Как могли эти два в остальном идентичных языка, которые и должны быть идентичны во имя всего доброго и святого в этом мире, разойтись в одном этом месте?

ХА! «Разойтись», как будто они были одинаковыми, пока не произошла мутация, а затем стали отдельными видами.

До того, как я присоединился к Microsoft, я, возможно, тоже смутно придерживался этой идеи и использовал ее в качестве аргумента, чтобы ответить противникам или кого-то успокоить. Но мне это понятно. Ее легко понять и очень легко повторять. Я понимаю ее очарование. Я работал с командой разработчиков и тестировщиков, чтобы реализовать заново каждый дюйм обоих языков, а также их инструментарий в огромном многопроектном солюшене с миллионами строк кода, написанными на обоих языках. Но работая над Roslyn (по сути переписывание VB и C# полностью с нуля) в течение 5 лет, я понял, насколько однозначно ложной является эта идея. На самом деле, иногда мне казалось, что я узнаю что-то новое о VB. И с учетом большого количества разработчиков, переключающихся между ними туда и обратно, и высокой планки совместимости с результатами и опытом предыдущих версий, а также необходимостью достоверно воспроизвести в мельчайших деталях гигантский объем API, я был вынужден очень близко познакомиться с различиями. NET (мой любимый язык) каждый день.

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

Прежде чем перейти к списку, я изложу основные правила:

  • Этот список не является исчерпывающим в обычном смысле. Он исчерпывающий силы. Это не все существующие различия. Это даже не все различия, которые я вообще знаю. Это просто различия, которые я могу вспомнить первыми, пока не слишком устану, чтобы продолжать; пока не исчерпаю силы. Если же я или кто-то из вас встретит или вспомнит другие различия, я с удовольствием обновлю этот список.
  • Я начну с начала спецификации VB 11 и двинусь вниз, используя ее содержание, чтобы напомнить себе о различиях, которые приходят в голову по этой теме первыми.
  • Это НЕ список функций в VB, которых нет в C#. Так что никаких «XML-литералы против указателей». Это слишком банально, и уже есть тонны таких списков в Интернете (некоторые из которых были написаны мной, и, возможно, в будущем я напишу еще). Я сфокусируюсь прежде всего на конструкциях, имеющих аналог в обоих языках, и где неосведомленный наблюдатель может предположить, что эти две вещи ведут себя одинаково, но где есть маленькие или большие различия; они могут одинаково выглядеть, но по-разному работать или генерировать в конечном счете разный код.
  • Это НЕ список синтаксических различий между VB и C# (которых бесчисленное множество). Я буду в основном говорить о семантических различиях (что вещи означают), а не о синтаксических (как вещи пишутся). Так что никаких штук типа «VB начинает комментарии с ', а C# использует //» или «в C# _ является допустимым идентификатором, но не в VB». Но я нарушу это правило для нескольких случаев. В конце концов, первый раздел спецификации посвящен лексическим правилам.
  • Довольно часто я буду приводить примеры, а иногда буду предлагать обоснования, почему дизайн мог пойти тем или иным путем. Некоторые решения по дизайну принимались на моих глазах, но подавляющее большинство предшествовало моему времени, и я могу только догадываться, почему они были приняты.
  • Пожалуйста, оставьте комментарий или напишите мне в Твиттере (@ThatVBGuy), чтобы сообщить мне ваши любимые отличия и/или те, о которых вы хотели бы узнать поглубже.

Определившись с ожиданиями и без дальнейших задержек…

Содержание

Скрытый текст

Синтаксис и препроцессинг

Объявления и пр.

  • 5. VB иногда пропускает объявления implements в IL для предотвращения случайной неявной реализации интерфейса по имени
  • 6. VB по умолчанию скрывает члены базового класса по имени (Shadows), а не по имени и сигнатуре (Overloads)
  • 7. VB11 и ниже являются более строгими к Protected-членам в дженериках
  • 8. Синтаксис «именованный аргумент (Named argument)» в атрибутах всегда инициализирует свойства / поля
  • 9. Все объявления верхнего уровня (обычно) неявно находятся в корневом пространстве имен проекта.
  • 10. Модули не генерируются как запечатанные (sealed) абстрактные классы в IL, поэтому они не похожи в точности на статические классы C# и наоборот
  • 11. Вам не нужен явный метод для точки входа (Sub Main) в приложениях WinForms
  • 12. Если вы вызываете некоторые устаревшие методы рантайма VB (например, FileOpen), вызывающий метод будет неявно помечен атрибутом, чтобы отключить инлайнинг из соображений корректности
  • 13. Если ваш тип помечен атрибутом DesignerGenerated и не содержит каких-либо явных объявлений конструктора, то генерируемый компилятором по умолчанию вызовет InitializeComponent, если он определен для этого типа.
  • 14. Отсутствие модификатора Partial НЕ означает, что тип не является partial
  • 15. В классах по умолчанию уровень доступа Public для всего, кроме полей, а в структурах Public и для полей тоже
  • 16. VB инициализирует поля ПОСЛЕ вызова базового конструктора, тогда как C # инициализирует их ДО вызова базового конструктора
  • 17. Неявно объявленное вспомогательное поле (backing field) событий VB имеет другое имя, нежели в C#, и доступно по имени
  • 18. Неявно объявленное вспомогательное поле авто-свойств VB имеет обычное имя и доступно по этому имени
  • 19. Неявно объявленное вспомогательное поле read-only авто-свойств позволяет запись
  • 20. Атрибуты событий иногда применяются к вспомогательному полю событий

Инструкции

Синтаксис и препроцессинг

1. Ключевые слова и операторы VB могут использовать полноширинные (full-width) символы

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

Согласно переводчику Bing, переменная называется «greeting», а в строке написано «Hello World!». Здесь у меня объявление переменной, написанное на японском языке, и инициализация строкой, также написанной на японском языке. Существуют полноширинные версии чисел и всех других печатных ASCII-символов, имеющие ту же ширину, что и японские. Имя переменной на японском составляет всего 2 символа, но оно занимает пространство из 4 символов половинной ширины, которые обычно выдает моя клавиатура, как демонстрирует первый комментарий. Это не такие «1» и «2», как в первом комментарии. Чтобы продемонстрировать это, я написал второй комментарий, используя полноширинные числа «1» и «2». Вы также можете видеть, что по размеру символы — не ровно 2 символа в ширину, там есть небольшое смещение. Между числами нет пробелов. Частично это происходит потому, что эта программа смешивает полноширинные и полуширинные символы в одной строке и во всех трех строках.

Мы не программисты, если не помешаны на выравнивании текста. Пробелы имеют половинную ширину, буквенно-цифровые символы — полную ширину. И мне кажется, что если вы китаец, японец или кореец (или кто-то еще, кто использует полноразмерные символы для своего языка) и используете идентификаторы или строки, написанные на родном языке, эти мелкие ошибки выравнивания приводят в бешенство.

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

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

Японцы используют VB. Да. За время работы в Microsoft я несколько раз встречал японцев VB MVP, по крайней мере, один из них постоянно приносил японские конфеты. На самом деле, несмотря на синтаксис, похожий на английский язык (а может именно поэтому), для большинства пользователей VB, которых я вижу на форумах, английский язык не основной. (В комментах автору написали, что японцы стараются везде в коде использовать ascii — Прим. Если вы VB-программист из Китая, Японии или Кореи (или из любой другой страны, которая использует полноширинные символы), пожалуйста, напишите в комментах. пер.)

Владимир Решетников (@vreshetnikov) обнаружил и исправил эту ошибку, так что великая традиция толерантности VB к ширине символов осталась в силе. Забавный момент: Когда я первоначально реализовал в VB интерполированные строки, я (к своему позору) не учел возможность полноширинных фигурных скобок в местах подстановки.

2. VB поддерживает «умные кавычки»

Ладно, это, конечно, мелочь, но достойная упоминания. Вы когда-нибудь видели пример кода в текстовом документе вроде такого:

А после копирования примера в ваш код обнаруживали, что ни одна из (подсвеченных) кавычек не работает, потому что Word заменяет все обычные кавычки ASCII на умные кавычки?

Ладно, у меня такое было, но только когда я копировал примеры на C#. Я — нет. пер.): В VB умные кавычки являются допустимыми разделителями для строк (забавно, что русские кавычки «» не работают — Прим.

Если вы удвоите умные кавычки для экранирования, то все, что вы получите во время выполнения, — это просто обычную («глупую») кавычку. Они также работают внутри строк, хотя, возможно, и странным образом. Компилятор НЕ заставляет вас обязательно заканчивать умной кавычкой или использовать правильную, если вы начали с умной, так что можете смешивать как угодно и не заморачиваться. Это может показаться немного странным, но тем не менее весьма практично, поскольку почти нигде больше не допускается наличие в строке умных кавычек. И да, это также работает с символом одинарных кавычек, используемым для комментариев:

В VB6 этого не было, так что кто-то добавил это позже. Я пытался заставить Пола Вика (@panopticoncntrl) признаться, что он сделал это исключительно потому, что замучился с этой проблемой при работе над спецификацией, но он отрицает вину.

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

4. В выражениях препроцессинга могут использоваться произвольные арифметические операторы

Объявления и пр.

5. VB иногда пропускает объявления implements в IL для предотвращения случайной неявной реализации интерфейса по имени.

Этот пункт из разряда эзотерики. В VB реализация интерфейса всегда делается явно. Но оказывается, что в отсутствие явной реализации поведение CLR по умолчанию при вызове метода интерфейса заключается в поиске публичных методов по имени и сигнатуре. В большинстве случаев это нормально, потому что в VB обычно требуется предоставить реализацию для каждого члена интерфейса, который вы реализуете, кроме одного случая:

Interface IFoo Sub Bar() Sub Baz()
End Interface Class Foo Implements IFoo Private Sub Bar() Implements IFoo.Bar Exit Sub End Sub Private Sub IFoo_Baz() Implements IFoo.Baz Exit Sub End Sub
End Class Class FooDerived Inherits Foo Implements IFoo Public Sub Bar() Implements IFoo.Bar Exit Sub End Sub Public Sub Baz() ' Does something unrelated to what an IFoo.Baz would do. End Sub
End Class

gist.github.com/AnthonyDGreen/39634fd98a0cacc093719ab62d7ab1e6#file-partial-re-implementation-vb

Bar на новый метод, но остальные реализации оставить без изменений. В этом примере класс FooDerived всего лишь хочет переназначить IFoo. Baz как новую реализацию IFoo. Оказывается, что если компилятор просто сгенерирует директиву implements для FooDerived, CLR также подхватит FooDerived. В C# это происходит неявно (и я не уверен, можно ли от этого отказаться), но в VB компилятор фактически опускает 'Implements' из всего объявления, чтобы этого избежать, и переопределяет только конкретные члены, которые были реализованы заново. Baz (хотя в этом примере он не связан с IFoo). Другими словами, если вы спросите FooDerived, реализует ли он IFoo напрямую, он скажет «нет»:

Почему я это знаю и почему это важно? В течение многих лет пользователи VB просили поддержку для неявной реализации интерфейса (без явного указания Implements в каждом объявлении), как правило, для кодогенерации. Просто включить это с текущим синтаксисом было бы breaking change, потому что FooDerived.Baz теперь неявно реализует IFoo.Baz, хотя раньше этого не делал. Но совсем недавно я узнал об этом поведении получше при обсуждении потенциальных проблем в дизайне фичи «реализация интерфейса по умолчанию», которая позволила бы интерфейсам включать реализации по умолчанию некоторых членов и не требовать повторной реализации в каждом классе. Это было бы полезно для перегрузок, например, когда реализация с большой вероятностью будет одинаковой для всех реализующих (делегирование основной перегрузке). Другой сценарий — версионирование. Если интерфейс может включать реализации по умолчанию, вы можете добавлять в него новые члены, не ломая старые реализации. Но тут возникает проблема. Поскольку поведение по умолчанию в CLR заключается в поиске общедоступных реализаций по имени и сигнатуре, если класс VB не реализует члены интерфейса с реализациями по умолчанию, но имеет публичные члены с подходящим именем и сигнатурой, они неявно реализуют эти члены интерфейса, даже если делать это совершенно не предполагалось. Есть вещи, которые можно сделать, чтобы это обойти, когда полный набор членов интерфейса известен во время компиляции. Но в случае, если член был добавлен после компиляции кода, он просто молча поменяет поведение во время выполнения.

6. VB по умолчанию скрывает члены базового класса по имени (Shadows), а не по имени и сигнатуре (Overloads)

Я думаю, это различие довольно хорошо известно. Сценарий таков: вы наследуете базовый класс (DomainObject), возможно, вне вашего контроля, и объявляете метод с именем, которое имеет смысл в контексте вашего класса, например, Print:

Class DomainObject
End Class Class Invoice Inherits DomainObject Public Sub Print(copies As Integer) ' Sends contents of invoice to default printer. End Sub
End Class

gist.github.com/AnthonyDGreen/863cfd1e7536fe8bda7cd145795eaf9f#file-shadows-example-vb

Но в следующей версии API, где объявлен ваш базовый класс, решают для отладки добавить всем DomainObject'ам метод, который выводит полное содержимое объекта в окно отладки. То, что инвойс может быть напечатан, имеет смысл. Проблема в том, что клиент вашего API может заметить, что объект Invoice имеет методы Print() и Print(Integer), и подумать, что это связанные перегрузки. Этот метод блестяще назвали Print. Но это совсем не то, что вы задумали как автор Invoice. Может, первый просто печатает одну копию. Print. Вы понятия не имели, что появится DomainObject. Когда всплывает такая ситуация, появляется предупреждение, но что более важно, поведение по умолчанию в VB — скрывать по имени. Так что да, в VB это работает не так. Клиентам вашего класса показывается только API, объявленный вами изначально. То есть пока вы явно не укажете с помощью ключевого слова Overloads, что ваш Print является перегрузкой Print базового класса, член базового класса (и любые его перегрузки) полностью скрыты. C# умеет только Overloads (хотя учитывает Shadows, когда ссылается на VB-шную библиотеку) и делает так по умолчанию (используя ключевое слово new). Так работает по умолчанию, но вы можете сделать это явно через ключевое слово Shadows. Но это различие всплывает время от времени, когда в проектах появляются некоторые иерархии наследования, где один класс определен на одном языке, а другой — на другом, и имеются перегруженные методы, но это выходит за рамки текущего пункта списка различий.

7. VB11 и ниже являются более строгими к Protected-членам в дженериках

На самом деле мы поменяли это между VS2013 и VS2015. В частности, мы решили не заморачиваться с повторной реализацией. Но я пишу это различие на случай, если вы используете старую версию и заметили его. Вкратце: если в дженерик-типе объявлен Protected-член, то наследник, будучи также дженериком, может получить доступ к этому protected-члену только через наследный экземпляр с такими же аргументами типа.

Class Base(Of T) Protected x As T
End Class Class Derived(Of T) Inherits Base(Of T) Public Sub F(y As Derived(Of String)) ' Error: Derived(Of T) cannot access Derived(Of String)'s ' protected members y.x = "a" End Sub
End Class

gist.github.com/AnthonyDGreen/ce12ac986219eb51d6c85fa02c339a2f#file-protected-in-generics-vb

8. Синтаксис «именованный аргумент (Named argument)» в атрибутах всегда инициализирует свойства/поля

VB использует тот же синтаксис := для инициализации свойств/полей атрибута, что и для передачи по имени аргументов метода. Следовательно, нет способа передать аргумент конструктору атрибута по имени.

9. Все объявления верхнего уровня (обычно) неявно находятся в корневом пространстве имен проекта

Это различие почти что в категории «дополнительные возможности», но я включил его в список, потому что оно меняет смысл кода. В свойствах проекта VB есть поле:

По умолчанию это просто имя вашего проекта в момент создания. Это НЕ то же самое поле, что «Default namespace» в свойствах проекта C#. Default namespace просто задает, какой код добавляется по умолчанию в новые файлы в C#. Но root namespace в VB означает, что, если не указано иное, каждое объявление верхнего уровня в этом проекте неявно находится в этом пространстве имен. Вот почему шаблоны документов VB обычно не содержат никаких объявлений пространств имен. Более того, если вы добавите объявление пространства имен, оно не переопределяет корневое, а добавляется к нему:

Namespace Controllers ' Child namespace.
End Namespace Namespace Global.Controllers ' Top-level namespace
End Namespace

gist.github.com/AnthonyDGreen/fd1e5e3a58aee862a5082e1d2b078084#file-root-namespace-vb

Controllers, если вы не избавитесь от этого механизма, явным образом объявив в пространстве имен Global. Таким образом, пространство имен Controllers фактически объявляет пространство имен VBExamples.

И это особенно полезно, если вы создаете UWP-приложение (потому что в UWP все должно быть в пространстве имен), и крайне удобно, если вы решите изменить пространство имен верхнего уровня для всего вашего проекта, скажем, с некоторого кодового имени типа Roslyn на более длинное релизное вроде Microsoft. Это удобно, потому что экономит один уровень отступов и одно лишнее понятие на каждый VB-файл. Также важно помнить это при работе с кодогенераторами, пространствами имен XAML и новым форматом файла .vbproj. CodeAnalysis, так как вам не придется вручную обновлять каждый файл в солюшене.

10. Модули не генерируются как запечатанные (sealed) абстрактные классы в IL, поэтому они не похожи в точности на статические классы C# и наоборот.

Модули в VB существовали до статических классов C#, хотя мы попытались в 2010 году сделать их одинаковыми с точки зрения IL. К сожалению, это было breaking change, потому что XML Serializer (а может это был binary) для той версии .NET (думаю, они исправили это) не хотел делать сериализацию типа, вложенного в тип, который не может быть создан (а абстрактный класс не может). Он бросал исключение.

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

11. Вам не нужен явный метод для точки входа (Sub Main) в приложениях WinForms

Если ваш проект использует Form в качестве стартового объекта и не использует «Application Framework» (подробнее об этом в следующем посте), VB генерирует Sub Main, который создает вашу стартовую форму и передает ее в Application.Run, экономя вам таким образом либо целый файл для управления этим процессом, либо дополнительный метод в вашем Form, либо вообще необходимость задумываться об этой проблеме.

12. Если вы вызываете некоторые устаревшие методы рантайма VB (например, FileOpen), вызывающий метод будет неявно помечен атрибутом, чтобы отключить инлайнинг из соображений корректности

Если кратко, методы для работы с файлами в стиле VB6 вроде FileOpen полагаются на контекст, специфичный для сборки, где находится код. Например, файл #1 может быть логом в одном проекте и конфигом в другом. Чтобы определить, какая сборка запущена, вызывается Assembly.GetCallingAssembly(). Но если JIT встраивает (inlines) ваш метод в вызывающий, то с точки зрения стека метод VB-рантайма будет вызван не вашим методом, а вызывающим, который может быть в другой сборке, что затем может позволить вашему коду получить доступ или нарушить внутреннее состояние вызывающего объекта. Это не вопрос безопасности, потому что, если компрометирующий код запущен в вашем процессе, вы уже проиграли. Это вопрос корректности. Поэтому, если вы используете эти методы, компилятор отключает инлайнинг.

Это изменение было сделано в последний момент в 2010 году, потому что x64 JIT ОЧЕНЬ агрессивен при инлайнинге/оптимизации кода, и мы обнаружили это очень поздно, и это был самый безопасный вариант.

13. Если ваш тип помечен атрибутом DesignerGenerated и не содержит каких-либо явных объявлений конструктора, то генерируемый компилятором по умолчанию вызовет InitializeComponent, если он определен для этого типа

В эпоху до появления Partial-типов команда VB вела войну за уменьшение бойлерплейт-кода в WinForms-проектах. Но даже при наличии Partial это полезно, потому что позволяет сгенерированному файлу полностью опустить конструктор, а пользователь может вручную объявить его в своем файле, если он нужен, или не объявлять, если нет. Без этого дизайнер был бы вынужден добавлять конструктор только чтобы вызывать InitializeComponent, а если пользователь добавит тоже, то они будут дубликатами, либо инструментарий должен быть достаточно умным, чтобы переместить конструктор из файла дизайнера в пользовательский и не генерировать его повторно в дизайнере, если он уже существует в пользовательском файле.

14. Отсутствие модификатора Partial НЕ означает, что тип не является partial

Технически в VB только один класс должен быть отмечен как Partial. Это обычно (в GUI-проектах) сгенерированный файл.

Однако рекомендуется, чтобы максимум один класс не имел модификатора Partial, в противном случае выдается предупреждение. Почему? Это сохраняет пользовательский файл красивым и чистым, и может быть очень удобным для включения после генерации или дополнения сгенерированного кода пользовательским.

15. В классах по умолчанию уровень доступа Public для всего, кроме полей, а в структурах Public и для полей тоже

У меня cмешанные чувства по этому поводу. В C# все по умолчанию private (ура, инкапсуляция!), но есть довод сделать в зависимости от того, что вы чаще объявляете: публичный контракт или детали реализации. Свойства и события, как правило, предназначены для внешнего (public) использования, и операторы не могут быть доступны иначе, как public. Однако я редко полагаюсь на доступность по умолчанию (за исключением демо наподобие примеров из этой статьи).

16. VB инициализирует поля ПОСЛЕ вызова базового конструктора, тогда как C# инициализирует их ДО вызова базового конструктора

Слышали, как «некоторые» говорят, что первое, что происходит в конструкторе, — это вызов конструктора базового класса? Ну, это не так, по крайней мере, в C#. В C# перед вызовом base(), явном или неявном, сначала выполняются инициализаторы полей, затем вызов конструктора, а затем ваш код. У этого решения есть последствия, и я думаю, что знаю, почему разработчики языка могли бы пойти тем или иным путем. Я считаю одно из таких последствий — что следующий код не может быть переведен в C# напрямую:

Imports System.Reflection Class ReflectionFoo Private StringType As Type = GetType(String) Private StringLengthProperty As PropertyInfo = StringType.GetProperty("Length") Private StringGetEnumeratorMethod As MethodInfo = StringType.GetMethod("GetEnumerator") Private StringEnumeratorType As Type = StringGetEnumeratorMethod.ReturnType Sub New() Console.WriteLine(StringType) End Sub
End Class

gist.github.com/AnthonyDGreen/37d01c8e7f085e06172bfaf6a1e567d4#file-field-init-me-reference-vb

И я смутно припоминаю коллегу до Microsoft (Джош), который переводил мой код на C#, иногда жалуясь на необходимость переносить все мои инициализаторы в конструктор. Во времена, когда я занимался Reflection, я часто писал такой код. И поскольку инициализаторы полей выполняются до указанного вызова, они также не могут ссылаться на другие поля или любые члены экземпляра объекта. В C# запрещено ссылаться на создаваемый объект до того, как будет вызван base(). Так что этот пример тоже работает только в VB:

MustInherit Class Base ' OOP OP? Private Cached As Object = DerivedFactory() Protected MustOverride Function DerivedFactory() As Object End Class Class Derived Inherits Base Protected Overrides Function DerivedFactory() As Object Return New Object() End Function
End Class

gist.github.com/AnthonyDGreen/fe5ca89e5a98efee97ffee93aa684e50#file-base-derived-init-vb

Есть много способов реализовать такой шаблон, но я обычно использую этот, потому что: Здесь у нас есть базовый класс, который предположительно имеет множество функциональных возможностей, но ему нужен некий ключевой объект для управления, работы, который определяется производным типом.

  • он краткий;
  • не требует от меня объявления конструктора;
  • не требует, чтобы я поместил код инициализации в конструктор, если он есть;
  • позволяет мне кэшировать созданный объект и не требует, чтобы производные типы объявляли и управляли хранилищем для предоставленного объекта, хотя теперь с автосвойствами это не такая проблема.

Далее, я бывал в обеих ситуациях: когда поле в производном типе хотело вызвать метод, объявленный в базовом классе, и когда инициализатору поля базового класса необходимо было вызвать MustOverride-член, реализованный производным типом. Оба допустимы в VB и ни один в C#, и в этом есть смысл. Если бы инициализатор поля C# мог вызвать член базового класса, этот член мог бы зависеть от полей, инициализированных в базовом конструкторе (который еще не запущен) и результаты почти наверняка были бы неправильными, и это никак не обойти.

В обратной ситуации все немного сложнее, потому что вызов Overridable-члена из инициализатора (или конструктора) базового класса может привести к доступу к полям до того, как они будут «инициализированы». Но в VB базовый конструктор уже заведомо отработал, так что вы можете делать что угодно! В моих сценариях такого просто не бывает. Но только ваша реализация знает, является ли это проблемой. Кроме того, четко определено, что происходит с полями до запуска пользовательских инициализаторов — они инициализируются значениями по умолчанию, как и все переменные в VB. Они не зависят от состояния экземпляра, но не могут быть Shared-членами, потому что вы не можете иметь Shared Overridable-член в любом языке по техническим причинам, выходящим за рамки этой статьи. Никаких сюрпризов.

NET, когда они это проектировали. Так почему же? На самом деле я не знаю, были ли мои сценарии тем, что имела в виду изначальная команда VB. Я думаю, что на самом деле все гораздо проще: дизайн VB гарантирует, что вы всегда можете написать в инициализаторе поля то, что вы могли бы написать в конструкторе. Просто в моем случае это реально работает! При таком дизайне они ими и являются по большому счету. Мы интуитивно думаем об инициализаторах полей как о более краткой форме присвоений в конструкторе.

Между прочим, это различие очень важно иметь в виду при разработке инициализаторов автосвойств и основных конструкторов, и это пример того, почему вы не можете просто скопипастить дизайн программы из C# в VB, особенно если этот дизайн опирается на идею, что ограничения в VB такие же, как в C#.

17. Неявно объявленное вспомогательное поле (backing field) событий VB имеет другое имя, нежели в C#, и доступно по имени

Это может быть важно в контексте рефлексии и сериализации (которая на самом деле просто еще бОльшая рефлексия). Если взять простое объявление события с именем E, в VB будет объявлено (скрытое в IDE) поле с именем EEvent. В C# поле также будет называться E, и язык имеет специальные правила, когда выражение E относится к событию, а когда к полю.

18. Неявно объявленное вспомогательное поле автосвойств VB имеет обычное имя и доступно по этому имени

Если объявить автосвойство с именем P, то сгенерируется поле с именем _P'. Оно скрыто в IntelliSense, но может быть доступно при необходимости. В C# это поле имеет «искаженное» (mangled) имя, что означает, что это имя не может быть объявлено или использовано непосредственно в C# и обычно содержит специальные символы.

Почему так? Команда VB решила использовать понятное имя, во-первых, поскольку оно в гармонии с решением по вспомогательным полям событий и переменными «WithEvents», и во-вторых, чтобы имя могло остаться прежним, если автосвойство когда-нибудь будет развернуто в обычное свойство, что важно для сохранения обратной совместимости при сериализации.

19. Неявно объявленное вспомогательное поле read-only автосвойств позволяет запись

Некоторые люди хотели бы, чтобы поля также были доступны только для чтения, для …чистоты. Но в VB существует сильная традиция наличия «спасательных люков» к его волшебным фичам. Хотя вспомогательные поля WithEvents-переменных, non-Custom событий и записываемых автосвойств почти никогда не предназначены для прямого доступа, все же есть скрытый способ обойти аксессоры, если этого требует ваша ситуация. Переменные скрыты от IntelliSense, поэтому вам надо будет приложить усилия, но если вам нужна гибкость, она есть. Философская самосогласованность FTW! Кроме того, это дает VB лаконичную фичу, сравнимую с объявлением private set; автосвойства в C#.

Class Alarm Private ReadOnly Code As Integer ReadOnly Property Status As String = "Disarmed" Sub New(code As Integer) Me.Code = code End Sub Sub Arm() ' I'm motifying the value of this externally read-only property here. _Status = "Armed" End Sub Function Disarm(code As Integer) As Boolean If code = Me.Code Then ' And here. _Status = "Disarmed" Return True Else Return False End If End Function
End Class

gist.github.com/AnthonyDGreen/57ce7962700c5498894ad417296f9066#file-read-only-auto-property-backing-field-is-writeable-vb

20. Атрибуты событий иногда применяются к вспомогательному полю событий

В частности, атрибут NonSerialized.

Это то, что вам обязательно нужно сделать, потому что объекты, слушающие ваши события, на самом деле не являются частью «вашего» состояния и не должны быть частью того, что считается вашим «контрактом данных». Поскольку в VB не было синтаксиса для объявления расширенного (expanded) Custom-события до 2005 года (?) и нет синтаксиса назначения атрибута для членов типа, было невозможно явно объявить вспомогательное поле для события и, таким образом, применить атрибут NonSerialized.

Так, если, например, у вас есть класс данных, который к тому же two-way bindable (а значит, объявляет событие PropertyChanged), сериализатор попытается сериализовать любые контролы, связанные с этим объектом, и, конечно, не сможет этого сделать. Это сильно мешало некоторым людям, желающим сериализовать объекты, потому что сериализатор попытался бы сериализовать вспомогательное поле события и, таким образом, всех слушателей события.

Таким образом, добавление этого особого случая действительно развязало руки затронутым клиентам. И пример этого, близкий и дорогой моему сердцу, можно найти в ранних версиях фреймворка CLSA «Expert Business Objects» Рокки Лотки (Rocky Lhotka), который использовал бы сериализацию для undo/redo (он сериализовал бы копию того, как объект выглядел раньше, когда вы что-то изменили, и десериализовал, если бы вы отменили изменения), а также клонирования объектов и сетевого маршаллинга. Кроме того, весьма изящно, что не нужно полностью заново писать событие вручную, чтобы отказаться от сериализации.

Инструкции

21. Область действия метки — это тело всего метода, ее содержащего; Вы можете прыгать внутрь блоков (не всех)

Так и есть, вы можете снаружи блока выполнить GoTo к метке внутри этого блока. Существуют некоторые ограничения, обычно когда такой переход позволит обойти инициализацию какой-либо важной языковой конструкции. Например, вы не можете перейти в циклы For или For Each; блоки Using, SyncLock или With, и я думаю, что также в некоторых случаях, включающих захват переменных в лямбде и блоки Finally. Но блоки If и Select Case, циклы Do и While, и даже блоки Try — это игра по правилам, и я встречал сценарии с каждым из них:


Module Program Sub Main() Dim retryCount = 0 Try
Retry: ' IO call. Catch ex As IO.IOException When retryCount < 3 retryCount += 1 GoTo Retry End Try End Sub
End Module

gist.github.com/AnthonyDGreen/b93adcf3c3705e4768dcab0b05b187a0#file-try-goto-retry-vb

NET в VB не было областей видимости типа «блок». Причиной этого, скорее всего, является тот факт, что до . Когда я начал писать на QB, отступы предлагалось использовать в стилистических целях. В VB6 и раньше вплоть до моего опыта с Quick Basic метки (и переменные) имели область видимости всего содержащего их метода. К тому же если вы собираетесь использовать GoTo, то вряд ли области видимости блоков для вас — бит старшего порядка, скорее это будет помехой на пути к цели. Это делало код более читабельным, но это не было отражением структуры «областей видимости», и достаточно часто весь мой код был выровнен по левому краю.

Важно: этот сценарий с Try нужно иметь в виду, если VB когда-нибудь получит поддержку await в блоках Catch и Finally, поскольку код, генерируемый при наличии такого GoTo должен быть немного другим.

22. Время жизни локальной переменной <> области видимости

Как продолжение предыдущего пункта, в VB время жизни (как долго эта переменная содержит значение) локальной (не static) переменной не совпадает с ее областью действия (где на нее можно ссылаться по имени). И это имеет смысл, особенно с учетом предыдущего пункта. В моем примере выполнение покинуло бы блок Catch на исключении и повторило попытку до 3 раз. Хотя любые внутренние переменные блока Try находятся вне области видимости блока Catch, и ссылаться на них там нельзя, разумно и необходимо, чтобы при повторном входе в блок Try эти переменные имели прежние значения.

NET видимость переменных была ограничена методом, и это не имело значения. Еще раз, до VB. Это также согласуется с опытом отладки: если во время отладки разработчик перемещает указатель инструкции обратно в блок, из которого вышел ранее. Но в любом случае на уровне CLR это верно даже без способности VB прыгать в блоки.

Просто в VB. Технически, C# определяет, что фактическое время жизни переменной зависит от реализации, поэтому поведение в отладчике не является «неправильным». NET фактическое время жизни гораздо заметнее.

23. Переменные всегда инициализируются значением по умолчанию для соответствующего типа

Сначала я не собирался об этом говорить, но это часто встречается в обсуждениях дизайна языка, потому что в C# есть такой хардкорный набор правил о «ясном присваивании» (definite assignment). Идея в том, что языку нужны правила, гарантирующие, что вы никогда не получите случайный доступ к «неинициализированной памяти». Это действительно опасно, если остаток (или код) в памяти от некоего предыдущего использования теперь загружен в переменную указателя, которая случайно разыменовывается, и ваше приложение вылетает или система уходит в синий экран. Это часть наследия C/C++. Потому что весь С про производительность, детка! Каждая операция драгоценна, и любое затрачиваемое ЦП время должно быть явным. Таким образом, автоматическое обнуление памяти в целях безопасности до того, как код ее использует, — это правильно. Если пользователь отчаянно хочет гарантированно не получить доступ к мусорным данным, он должен написать это явно, чтобы было ясно, что он просит заплатить за эти циклы ЦП и, в случае, если он все равно уже написал идеально оптимизированный алгоритм, инициализирующий эту переменную неким ненулевым значением, то во всяком случае он не заплатит и за обнуление, и за явную инициализацию. Но да, языки BASIC так не думают, поэтому все наши переменные автоматически инициализируются до значения по умолчанию, и нет никакого доступа к «случайной» памяти, поэтому не требуется для каждой переменной = Nothing, = 0, = False и т.д.

Следовательно, анализ потока (flow analysis) в VB больше похож на рекомендации, чем на реальные правила.

VB имеет предупреждения в некоторых подобных ситуациях, но изначально они были направлены на то, чтобы помочь разработчикам найти потенциальные источники нулевых ссылок, а не на поощрение более избыточных инициализаторов. Правила ясного присваивания также облагают большим налогом дизайн языка, потому что правила C# должны быть герметичными, чтобы гарантировать, что вы никогда не получите доступ к переменной в месте, где она не была ясно присвоена по любому пути к этому месту. В Roslyn, однако, API все еще используют более строгое определение «ясного присваивания», так что ощущения от рефакторинга на высоте, хотя технически переменные всегда ясно присваиваются.

24. RaiseEvent НЕ выдает исключение, если вспомогательное поле равно null

Я видел такое несколько раз, когда кто-то пытался перевести некоторый код C# на VB. RaiseEvent в VB — это не просто перевод прямого вызова вспомогательного поля, он фактически проверяет на null (потокобезопасным способом), поэтому ситуация с null-обработчиком — не то, о чем вам вообще стоит задумываться.

' You don't have to write this:
If PropertyChangedEvent IsNot Nothing Then RaiseEvent PropertyChanged(Me, e)
End If ' You don't have to write this:
Dim handlers = PropertyChangedEvent
If handlers IsNot Nothing Then handlers(Me, e)
End If ' You don't have to write this either:
PropertyChangedEvent?(Me, e) ' Just write this:
RaiseEvent PropertyChanged(Me, e)

gist.github.com/AnthonyDGreen/c3dea3d91ef4ffc50cfa92c41f967937#file-null-safe-event-raising-vb

NET продолжит хорошо служить вам. Следовательно, хотя использование синтаксиса null-conditional вызова в C# с VS2015 является большим выигрышем для C# в этой ситуации, это гораздо меньший выигрыш для VB (хоть и выигрыш), и я бы никому не советовал заморачиваться, чтобы использовать его без необходимости; идиоматический код VB.

25. Присвоения не всегда одинаковы; иногда присвоение ссылочного типа выполняет поверхностную копию (shallow clone)

Это одно из тех отличий, которые, если вы не заметили их за последние 17 лет, вероятно, не имеют для вас значения. Когда вы присваиваете упакованный (boxed) значимый тип в переменную типа Object, компилятор вставляет вызов метода с именем System.Runtime.CompilerServices.RuntimeHelper.GetObjectValue. Это специальный метод, реализованный внутри CLR. Вот что он делает, принимая ссылку на объект:

  • Если объект является ссылочным типом, он возвращает эту ссылку без изменений.
  • Если объект представляет собой упакованный значимый тип, который является неизменяемым (например, все примитивные типы типа Integer ), он возвращает эту ссылку без изменений.
  • Если объект является любым другим упакованным значимым типом, он копирует значение в новое упакованное значение и возвращает ссылку на него.

Это делается для того, чтобы сохранить семантику значимых типов, которая говорит, что значения всегда копируются, даже в ситуациях с поздним связыванием (late-bound situations). Поэтому, даже если у меня есть упакованная изменяемая структура, и я передаю ее (все еще в упакованном виде) в метод, и этот метод выполняет операции с поздним связыванием над упакованным объектом, которые его изменяют, он все равно работает только с копией значения, а не с копией вызывающего (caller’s copy). Так что будь ваш код слабо типизированным и полностью динамическим, строго типизированным с ранним связыванием, или чем-то средним, — семантика значимых типов остается прежней.

Это была программа вроде такой: Я столкнулся с этим ровно один раз в своей карьере.

Class MyEventArgs Property Value As Object
End Class Structure MyStruct Public X, Y As Integer
End Structure Module Program Sub Main() Dim defaultValue As Object = New MyStruct With Dim e = New MyEventArgs With {.Value = defaultValue} RaiseEvent DoSomething(Nothing, e) If e.Value Is defaultValue Then ' No handlers have changed anything. Console.WriteLine("Unchanged.") End If End Sub Event DoSomething(sender As Object, e As MyEventArgs) End Module

gist.github.com/AnthonyDGreen/422ac4574af92d9bbbf59f0fbc40b74d#file-get-object-value-vb

Если ничего не изменилось, то код пойдет коротким путем. Там было нечто вроде конвейера событий, похожего на конвертер значений WPF, где код начинается со значения по умолчанию и дает другому коду возможность изменить это значение. Но вскоре я понял, что так никогда происходило. Логично, что если я начал с упакованного значения и аргументы события ссылались на один и тот же упакованный объект после вызова события, то ни один из обработчиков не обновил значение. Я не думаю, что вообще мог как-то обойти это поведение, поэтому я, вероятно, отказался от использования упакованного значимого типа и заменил свое значение по умолчанию на класс.

Я не проверял IronRuby/Python, но я проверил dynamic в C# (и компилятор C#): C# не добавляет вызовы GetObjectValue при присваивании между динамическими типами. Кстати, в документации хелпера указано, что другие «динамические языки» также могут использовать этот хелпер для сохранения семантики значений. ReferenceEquals, чтобы понять, были ли ссылки одинаковыми, и это делало копию упакованного значения где-то глубоко в недрах dynamic C# (потому что это был динамический вызов). Моим первым инстинктом при проверке этого было использовать object. Так что C#, по крайней мере иногда, разделяет эту цель сохранения семантики значений в ситуациях позднего связывания. Но когда я перешел на использование ==, оно не делало копию.

26. Select Case не поддерживает «проваливание» (fall-through); не требуется break

В приведенном ниже коде Friday является единственным рабочим днем, а Sunday — единственным выходным, остальные 5 дней недели теряются.

Module Program Sub Main() Select Case Today.DayOfWeek Case DayOfWeek.Monday: Case DayOfWeek.Tuesday: Case DayOfWeek.Wednesday: Case DayOfWeek.Thursday: Case DayOfWeek.Friday: Console.WriteLine("Weekday") Case DayOfWeek.Saturday: Case DayOfWeek.Sunday: Console.WriteLine("Weekend") End Select End Sub
End Module

gist.github.com/AnthonyDGreen/7b7e136c71dd11b2417a6c7267bb3546#file-select-case-no-fallthrough-vb

Оно не проваливается!» Я отвечаю «Да, не проваливается». Однажды разработчик из команды Roslyn C# перезвонил мне, открыл какой-то код на своем ноутбуке и сказал: «Знаешь, что я сегодня выяснил? VS фактически удаляет эти двоеточия, если вы их набираете, но так сложилось, что код был сгенерирован, и никто не проверял сгенерированный код, он просто не работал правильно. Было много смеха. Но мы его исправили!

C# разработан, чтобы быть знакомым разработчикам из семейства языков C, и именно так работают свитчи в C. Так что это различие по классической причине. Между прочим, C# технически тоже не поддерживает проваливание, если только раздел case не полностью пустой. Они проваливаются от одного кейса к другому. В этом контексте в VB существует эквивалент break, Exit Select, но он не нужен в конце блока, потому что в VB нет никакого проваливания. Если вы что-то туда положили, вам нужно или явное goto, или break.

27. Каждый блок Case имеет свою область видимости

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

Module Program Sub Main() Select Case Today.DayOfWeek Case DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday Dim message = "Get to work!" Case DayOfWeek.Saturday, DayOfWeek.Sunday Dim message = "Funtime!" End Select End Sub
End Module

gist.github.com/AnthonyDGreen/bd642061896246c9336255881fb78546#file-select-case-scopes-vb

Они не объявляют область видимости. Ошибка будет означать, что message уже объявлена и не может быть объявлена дважды, потому что в C# весь оператор switch представляет собой одну область видимости и каждая метка case — это просто метка. Что, я полагаю, в какой-то степени имеет смысл (по крайней мере, в C): если вы проваливаетесь от одной секции к другой, то, возможно, существует состояние, которое необходимо разделить между секциями.

28, 29, 30. Select Case работает с непримитивными типами, может использовать произвольные неконстантные выражения в проверках и по умолчанию использует оператор =

Итак, вот вещь, которую я вообще не знал, пока на митинге по дизайну языка не стали отталкиваться от ошибочных представлений, какой код с использованием Select Case может существовать сегодня и как вокруг него должны будут проектироваться потенциальные новые фичи.

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

  • Select Case — это сокращение, когда вы хотите протестировать одно и то же значение несколько раз, но…
  • switch — это быстрая инструкция/нативная операция, известная как «таблица ветвлений или переходов».

Это различие, которое объединяет различия 26-30. switch в прошлом ограничивался сценариями, в которых производительность кода, сгенерированного компилятором, выше, чем несколько последовательных проверок if. В IL есть инструкция switch, которая намного эффективнее, чем несколько If, и компилятор VB будет использовать ее в качестве оптимизации при условии, что она быстрее. Но по философии switch в прошлом был ограничен только такими сценариями, полагаю, как наследник веры C в прозрачность производительности. В VB это просто удобство самовыражения.

31. Переменные, объявленные внутри циклов, в некотором роде сохраняют свое значение между итерациями

В этом примере каждую итерацию цикла x имеет то же значение, с которым она завершила предыдущую итерацию, поэтому в консоль выведутся числа -1, -2, -3:

Module Program Sub Main() For i = 1 To 3 Dim x As Integer x -= 1 Console.WriteLine(x) Next End Sub
End Module

gist.github.com/AnthonyDGreen/cbc3a9c70677354973d64f1d993a3c5d#file-loop-variables-retain-their-values-vb

Это тонкое различие, которое стало заметным только в VB2008 и дальше, когда были введены лямбда-выражения: Технически каждая итерация «делает свежую копию всех локальных переменных, объявленных в этом теле, инициализированную предыдущими значениями переменных» (согласно спецификации).

Module Program Sub Main() Dim lambdas = New List(Of Action) For i = 1 To 3 Dim x As Integer x -= 1 lambdas.Add(Sub() Console.WriteLine(x)) Next For Each lambda In lambdas lambda() Next End Sub
End Module

gist.github.com/AnthonyDGreen/2ef9ba3dfcf9a1abe0e94b0cde12faf1#file-loop-variables-captured-per-iteration-vb

Потому что технически каждая x — «свежая копия», лямбда-выражение захватывает значение x только для этой итерации, что чаще всего соответствует вашим ожиданиям. Этот пример также выводит -1, -2, -3. Попробуйте посмотреть это во flow analysis API — рискните! Но вы все равно должны перенести значение из предыдущих итераций, как если бы это была одна переменная x на все время жизни цикла. («Переменная… присваивается… самой себе?»)

Это лучший способ добавить область видимости на уровне блока, сохранив поведение переменной с временем жизни уровня метода, а как только вы добавляете сюда лямбда-выражения, копирование переменной становится единственным практичным и интуитивно понятным вариантом. Почему? Я поспрашивал окружающих, и те, кто был в команде дольше всех, не смогли вспомнить точно, но если подумать на эту тему, то это имеет смысл в совокупности с #22.

Это на 10000% более практично и интуитивно, чем было раньше (на самом деле, VB раньше показывал предупреждении в этом сценарии, потому что поведение было слишком неинтуитивно). Кстати, команды разработчиков VB и C# решили изменить поведение управляющих переменных (control variables) в For Each в VS2012 (?), чтобы лямбда-выражения захватывали их «на итерацию». То есть чтобы вы все еще могли изменить их значение внутри цикла, но после захвата текущее значение замораживалось. Кроме того, команда разработчиков языка VB очень серьезно рассматривала изменение поведения управляющих переменных в For, чтобы они вели себя как переменные внутри цикла. В конечном счете мы так и не внесли это изменение, но цикл For в VB по-прежнему выдает предупреждение, когда захватывается управляющая переменная, потому что поведение часто становится сюрпризом. Это изменение рассматривалось вместе с идеей, что в VB циклы For были намного ближе к циклам For Each, чем for к foreach в C#.

32. Три выражения цикла For вычисляются только один раз в начале цикла

После начала вы не можете изменить границы цикла For. Выражения в заголовке цикла вычисляются только один раз, а результаты кэшируются, поэтому в этом примере будет напечатано 1,3,5,7,9, даже если изменение верхней границы и инкремента заставляет вас думать, что он будет крутиться вечно.

Module Program Sub Main() Dim lower = 1, upper = 9, increment = 2 For i = lower To upper Step increment Console.WriteLine(i) upper += 1 increment -= 1 Next End Sub
End Module

gist.github.com/AnthonyDGreen/1e48113be204f515c51e221858666ac7#file-for-loop-bounds-cached-vb

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

Вы когда-нибудь задумывались, как VB понимает, цикл For i = a To b Step c считает вверх (и должен остановиться, когда i> b ) или вниз (и должен остановиться, когда i <b ), особенно если c неизвестна во время компиляции? Тем не менее, я не уверен, что мир будет работать без этого, учитывая, что, в отличие от цикла в стиле C, условие цикла в VB выводится. Я даже не уверен, чего в этом случае ожидать, но, благо, мне никогда не придется об этом думать. Это довольно захватывающее чтение, особенно при позднем связывании, но этот карточный домик рухнул бы, если бы b иногда было положительным, а иногда — отрицательным.

33. For Each циклы в VB могут использовать метод расширения GetEnumerator

Чтобы тип можно было использовать в For Each, типу не нужно реализовывать IEnumerable, компилятору просто должен быть доступен метод GetEnumerator в коллекции, по которой делается For Each.
Например, я всегда считал, что должна быть возможность For Each по IEnumerator в ситуациях, когда вы уже использовали его часть и хотите возобновить итерирование, например:

Module Program Sub Main() Dim list = New List(Of Integer) From {1, 2, 3, 4, 5} Dim info = list.FirstAndRest() If info.First IsNot Nothing Then Console.Write(info.First.GetValueOrDefault()) For Each other In info.Additional Console.Write(", ") Console.Write(other) Next Console.WriteLine() End If End Sub <Runtime.CompilerServices.Extension> Function FirstAndRest(Of T As Structure)(sequence As IEnumerable(Of T)) As (First As T?, Additional As IEnumerator(Of T)) Dim enumerator = sequence.GetEnumerator() If enumerator.MoveNext() Then Return (enumerator.Current, enumerator) Else Return (Nothing, enumerator) End If End Function <Runtime.CompilerServices.Extension> Function GetEnumerator(Of T)(enumerator As IEnumerator(Of T)) As IEnumerator(Of T) Return enumerator End Function
End Module

gist.github.com/AnthonyDGreen/d7dbb7a5b98a940765c4adc33e3eaeee#file-for-each-extension-get-enumerator-vb

В этом примере я взял очередь у моих друзей из F# и разделил последовательность на первый элемент и остаток, а также расширил IEnumerator, чтобы я мог выполнить For Each на всех неиспользованных элементах, оставшихся в последовательности.

Это также относится, например, к методу Add, используемому инициализаторами коллекции. В VB есть общая тема, что когда языковой конструкции нужно найти член с хорошо известным именем (well-known name), этот член может быть методом расширения. async/await). C# по умолчанию так не делает, но с каждой версией относится к этому все проще (см. На самом деле там был баг, в котором компилятор C# Roslyn (случайно) делал это для инициализаторов коллекций, и они решили его оставить.

15-16 мая в Санкт-Петербурге состоится конференция для . Минутка рекламы. Будет множество докладов, касающихся деталей работы и внутреннего устройства платформы. NET-разработчиков DotNext 2019 Piter. На официальном сайте можно ознакомиться с программой и приобрести билеты. Программа всё ещё находится на этапе формирования, но около половины докладов уже известны.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть