VB.NET和C#之间的差异的详尽列表。 第一部分

图片

根据2018年TIOBE 评级 ,VB.NET的受欢迎程度超过了C#。 是否巧合,但在2月,C#的创建者之一埃里克·利珀特(Eric Lippert) 敦促读者注意他的朋友的博客,他的朋友是罗斯林编译器的前队友,并且是热情的VB.NET爱好者安东尼·格林Anthony Green) 。 “这些资源是来自专家的深入细节,通过阅读文档很难找到,” Eric写道。 我们提请您注意Anthony Green的文章的第一部分“ VB.NET与C#之间的差异的详尽列表”。 也许正是由于这些差异,才使这些语言的评级动态化的秘密所在。

在我将近一半的生命中,我目睹并参与了无数关于两种最流行的.NET语言的相似或不同之处的讨论。 首先,作为一个业余爱好者,然后是一个专业人士,最后,作为客户拥护者,程序经理和语言设计师,我可以毫不夸张地说出我听到或阅读过以下内容的次数:
“ ... VB.NET实际上只是IL之上的一个薄层,就像C#一样……”
“ ... VB.NET实际上只是C#,没有分号...”
好像这些语言是XML转换或样式表一样。

而且,如果某些热心的访客没有在评论中写这个,那么通常这暗示着这样的问题: “你好,安东尼! 我在一个地方遇到了这么小的差异-这是一个错误吗? 在这世界上所有善良和圣洁的名义上应该完全相同的这两种原本相同的语言如何在这个地方散布? 为什么我们需要这种不公正?

直到出现突变为止,它们都是相同的, 然后变成独立的物种。 哈哈!

但我明白。 在加入Microsoft之前,我可能还模糊地坚持了这个想法,并将其用作回应对手或使某人放心的论据。 我了解她的魅力。 这很容易理解,很容易重复。 但是在Roslyn上工作(基本上完全从头重写VB和C#)长达5年,我意识到这个想法是多么明确 。 我与一个开发人员和测试人员团队合作,在一个巨大的多项目解决方案中重新实现了这两种语言的每一寸,以及它们的工具,该解决方案具有数百万种用两种语言编写的代码行。 考虑到大量开发人员之间的来回切换,以及与以前版本的结果和经验的高度兼容性以及需要以最小的细节可靠地复制大量API的需求,我被迫非常仔细地了解它们之间的差异。 实际上,有时候在我看来,我每天都学习有关VB.NET(我最喜欢的语言)的新知识。

最后,我花时间坐下来,从大脑中卸载过去15年来我学习使用和创建VB.NET所学知识的一部分,希望我至少可以节省下一次的时间。

在继续之前,我将概述基本规则:

  • 通常情况下,此列表并不详尽。 他很穷。 这些并不是所有的差异。 这些甚至都不是我通常知道的所有差异。 这些只是我首先要记住的差异,直到我累得无法继续为止。 直到我筋疲力尽。 如果我或你们中的任何一个遇到或忆起其他分歧,我将很乐意更新此列表。
  • 我将从VB 11规范的开头开始,然后使用其内容向下移动,以提醒自己该主题首先引起的差异。
  • 这不是VB中C#以外的功能的列表。 因此,没有“ XML文字与指针”。 这太普通了,互联网上已经有很多这样的列表(其中一些是我写的,也许将来我会写更多)。 我将主要关注在两种语言中都具有类似物的构造,并且不知情的观察者可能会建议这两种事物的行为相同,但是差别不大。 它们可能看起来相同,但工作方式不同,或者最终生成不同的代码。
  • 这不是VB和C#之间的语法差异 (无数)的列表。 我将主要讨论语义差异(事物的含义),而不是语法差异(事物的编写方式)。 因此,没有类似“ VB以'开头的注释,而C#使用//”或“在C#_中是有效的标识符,但在VB中不是有效的标识符”之类的词。 但是我将在若干情况下违反此规则。 毕竟,规范的第一部分是关于词汇规则的。
  • 我经常会举一些例子,有时我会提供理由说明为什么设计可以采用一种方法。 有些设计决策是在我眼前做出的,但是绝大多数决策是在我时代之前完成的,我只能猜测为什么做出这些决策。
  • 请发表评论或鸣叫我( @ThatVBGuy ),让我知道您最喜欢的差异和/或您想了解的更多差异。

确定了期望并且没有进一步的延迟...

目录内容


隐藏文字

语法和预处理



公告等



使用说明



语法和预处理


1. VB关键字和运算符可以使用全角字符


在某些语言中(我不知道确切多少,但至少在某些形式的中文,日文和韩文中)使用全角字符。 简而言之,这意味着当使用等宽字体时(像大多数程序员一样),汉字占据的水​​平空间是我们过去在西方看到的拉丁字符的两倍。 例如:



在这里,我有一个用日语编写的变量声明,以及一个用日语编写的字符串进行初始化的方法。 根据Bing翻译人员的说法,该变量称为“ greeting”,而该行显示为“ Hello World!”。 日语中的变量名只有2个字符长,但是它占用了我的键盘通常会给出的4个半角字符的空间,如第一个注释所示。 有数字的全角版本和所有其他印刷的ASCII字符,其宽度与日语相同。 为了证明这一点,我用全角数字“ 1”和“ 2”写了第二条评论。 这些与第一条评论中的“ 1”和“ 2”不同。 数字之间没有空格。 您还可以看到字符的大小不完全是2个字符宽,略有偏移。 部分原因是因为该程序在一行和所有三行中混合了全角和半角字符。

空格为半角,字母数字字符为全角。 除非我们沉迷于文本对齐,否则我们不是程序员。 在我看来,如果您是中国人,日本人或韩国人(或其他使用全角字符作为其语言的人)并使用以其本国语言编写的标识符或字符串,那么这些较小的对齐错误会令人发指。

据我了解,根据您的日语键盘,在象形文字和拉丁语之间进行切换很容易,但是最好使用全角拉丁字符。 VB在关键字,空格,运算符甚至引号中都支持此功能。 所以所有这些可以这样写:



如您所见,在此版本中,关键字,空格,注释,运算符,甚至引号都使用其完整版本。 陷入混乱带来秩序。

是的 日本人使用VB。 实际上,尽管语法类似于英语(也许就是这个原因),但对于我在论坛上看到的大多数VB用户而言,英语并不是主要语言。 在微软工作期间,我多次遇到日本VB MVP,其中至少有一位经常带来日本糖果。 如果您是来自中国,日本或韩国(或来自使用全角字符的任何其他国家)的VB程序员,请在评论中写。 (在对作者的评论中,他们写道,日语在代码中到处都在尝试使用ascii- 大约。

有趣的时刻:当我最初在VB中实现插值行时,(令我感到羞耻)我没有考虑替换位置使用全角花括号的可能性。 Vladimir Reshetnikov( @vreshetnikov )发现并纠正了此错误,因此VB宽字符宽容的传统一直有效。

2. VB支持智能报价


好的,这当然是一件小事,但值得一提。 您是否曾经在这样的文本文档中看到示例代码:



在将示例复制到代码中之后,您是否发现所有(突出显示的)引号都不起作用,因为Word用智能引号替换了所有常见的ASCII引号?

我不知道 好的,我知道了,但是只有当我将示例复制到C#时才知道。 在VB中,智能引号是字符串的有效分隔符(有趣的是,俄罗斯引号«»无效-大约。):



它们也可以在字符串内部工作,尽管可能以一种奇怪的方式。 如果将巧妙的引号加倍以进行转义,则在运行时获得的只是纯(“哑”)引号。 这似乎有些奇怪,但是却非常实用,因为字符串中几乎所有其他地方都允许使用智能引号。 如果您从智能引号开始,编译器并不会使您肯定以智能引号结尾或使用正确的引号,因此您可以混合使用而不必担心。 是的,这也适用于用于注释的单引号字符:



我试图让Paul Wick( @panopticoncntrl )承认他这样做是因为他在制定规范时饱受这个问题的折磨,但他否认有罪恶感。 在VB6中不是这种情况,因此以后有人添加了。

3.预处理常量可以是任何原始类型(包括日期),并且可以包含任何常量值



4.算术运算符可用于预处理表达式




公告等


5. VB有时会跳过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

在此示例中, FooDerived类仅想将IFoo.Bar重新分配给新方法,而其余实现则保持不变。 事实证明,如果编译器仅生成FooDerived的Implements指令,则CLR还将FooDerived.Baz用作FooDerived.Baz的新实现(尽管在此示例中与IFoo不相关)。 在C#中,这是隐式发生的(我不确定是否可以拒绝它),但是在VB中,编译器实际上从整个声明中省略了“实现”来避免这种情况,并仅重新定义了已重新实现的特定成员。 换句话说,如果您询问FooDerived是否直接IFoo ,它将拒绝:


为什么我知道这一点,为什么它很重要? 多年来,VB用户一直要求支持接口的隐式实现(在每个声明中未明确指定Implements ),通常用于代码生成。 仅将其与当前语法结合起来将是FooDerived.Baz重大突破,因为FooDerived.Baz现在隐式实现了IFoo.Baz ,尽管以前没有这样做。 但是最近,我在使用“默认接口实现”功能讨论潜在的设计问题时了解了更多有关此行为的信息,该功能将允许接口包括某些成员的默认实现,而无需在每个类中重新实现。 这对于重载很有用,例如,当所有实现者的实现可能都相同时(委派给主重载)。 另一种情况是版本控制。 如果接口可以包含默认实现,则可以在不破坏旧实现的情况下向其添加新成员。 但是有一个问题。 由于CLR中的默认行为是按名称和签名搜索公共实现,因此,如果VB类未使用默认实现来实现接口成员,但具有具有适当名称和签名的公共成员,则即使您完全完成此操作,它们也会隐式实现这些接口成员。不应该。 当在编译时知道完整的接口成员集时,您可以采取一些措施来解决此问题。 但是,如果成员是在编译代码后添加的,则它只是在运行时以静默方式更改行为。

6. VB默认情况下按名称(阴影)隐藏基类的成员,而不按名称和签名(过载)隐藏基类的成员


我认为这种差异是众所周知的。 场景是这样的:您继承了基类( 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的下一版本中,它们决定进行调试,以向所有DomainObjects添加一个方法,该方法在调试窗口中显示对象的全部内容。 这种方法被巧妙地称为“打印”。 问题在于您的API的客户端可能会注意到Invoice对象具有Print()Print(Integer)方法,并认为这些是相关的重载。 也许第一个只打印一份。 但是,这根本不是您所认为的发票的作者。 您不知道DomainObject.Print将出现。 是的,这在VB中不起作用。 出现这种情况时,会出现警告,但更重要的是,VB中的默认行为是按名称隐藏。 也就是说,在您用Overloads关键字明确指出您的Print是基类的Print的重载之前Print基类的成员(及其任何重载)都将被完全隐藏。 只有您最初声明的API才会显示给类客户端。 默认情况下,此方法有效,但是您可以通过Shadows关键字显式地进行操作。 C#只能执行Overloads (尽管在引用VB库时会考虑Shadows ),并且默认情况下会这样做(使用new关键字)。 但是,当某些继承层次结构出现在以一种语言定义另一种语言并用另一种语言定义另一类并且有重载方法的项目中时,这种差异会不时出现,这超出了差异列表中当前项目的范围。

7.对于泛型中的受保护成员,VB11及以下版本更加严格


实际上,我们在VS2013和VS2015之间进行了更改。 特别是,我们决定不再为重新实施而烦恼。 但是,如果您使用旧版本并注意到它,我正在写这个区别。 简而言之:如果以通用类型声明了受保护的成员,那么继承人(也是通用的)只能通过具有相同类型参数的继承实例来访问此受保护成员。

 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 yx = "a" End Sub End Class 

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

8.属性中的语法“命名参数”始终会初始化属性/字段


VB使用相同的语法:=来初始化属性属性/字段,就像通过名称传递方法参数一样。 因此,无法通过名称将参数传递给属性构造函数。

9.所有顶级声明(通常)都隐式位于项目的根名称空间中


这种差异几乎在“高级功能”类别中,但是我将其包括在列表中,因为它改变了代码的含义。 VB项目的属性中有一个字段:


默认情况下,这只是创建时项目的名称。 该字段与C#项目的属性中的“默认名称空间”不同。 默认名称空间只是设置默认情况下将哪些代码添加到C#中的新文件中。 但是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

因此,除非您通过在命名空间中显式声明Global来摆脱此机制,否则Controllers命名空间实际上会声明VBExamples.Controllers命名空间。

这很方便,因为它为每个VB文件节省了一层缩进和一个额外的概念。 这在创建UWP应用程序时特别有用(因为所有内容都必须在UWP的名称空间中),如果决定将整个项目的顶级名称空间从Roslyn之类的代码名称更改为Roslyn,这将非常方便。较长版本的文件,例如Microsoft.CodeAnalysis ,因为您不必手动更新解决方案中的每个文件。 使用代码生成器,XAML名称空间和新的.vbproj文件.vbproj时,请记住这一点也很重要。

10.模块不是在IL中作为密封的抽象类生成的,因此它们看起来并不完全像静态C#类,反之亦然。


尽管我们在2010年尝试使VB中的模块在IL方面相同,但VB中的模块存在于静态C#类之前。 不幸的是,这是一个重大更改,因为该版本的.NET(我认为他们已将其修复)的XML序列化器(或者可能是二进制)不希望将嵌套的类型序列化为无法创建的类型(以及抽象类)不能)。 他抛出了一个例外。

我们在进行更改后将其找到并回滚,因为某些地方的某些代码使用了嵌入在模块中的枚举类型。 而且由于您不知道编译程序将使用哪个版本的序列化程序,因此将永远无法对其进行更改,因为在一个版本的应用程序中它将运行,而在其他情况下,它将引发异常。

11.在WinForms应用程序中,不需要显式的入口点方法(Sub Main)


如果您的项目使用Form作为起始对象,而不使用“ Application Framework”(在下一篇文章中有更多内容),则VB会生成Sub Main ,它会创建您的起始表单并将其传递给Application.Run ,从而可以保存整个文件要管理此过程,可以使用Form的其他方法,甚至需要考虑此问题。

12.如果调用一些过时的VB运行时方法(例如FileOpen),则调用方法将隐式地标记有属性,以出于正确性的原因禁用内联


简而言之,使用VB6样式的文件(如FileOpen依赖于特定于代码所在程序集的上下文。 例如,文件1可以是一个项目中的日志,而另一个项目中的配置。 要确定哪个程序集正在运行, Assembly.GetCallingAssembly()调用Assembly.GetCallingAssembly() 。 但是,如果JIT将您的方法内到调用程序中,则从堆栈的角度来看,VB运行时方法将不会由您的方法调用,而是由调用程序(可能位于不同的程序集中)调用,然后可以使您的代码访问或违反调用程序的内部状态。对象。 这不是安全问题,因为如果破坏性代码正在您的进程中运行,那么您已经迷路了。 这是正确性的问题。 因此,如果使用这些方法,编译器将禁用内联。

这项更改是在2010年的最后一刻进行的,因为x64 JIT在内联/优化代码时非常激进,我们发现它很晚了,这是最安全的选择。

13.如果您的类型标记有DesignerGenerated属性,并且不包含任何显式的构造函数声明,则默认生成的编译器(如果为此类型定义)将调用InitializeComponent。


在Partial类型出现之前的时代,VB团队发动了一场战争,以减少WinForms项目中的样板代码。 但是,即使使用Partial它也是有用的,因为它允许生成的文件完全省略构造函数,并且用户可以在需要时在其文件中手动声明它,或者在不需要时不声明它。 否则,设计人员将被迫仅向调用InitializeComponent添加一个构造函数,并且如果用户添加的话,它们将是重复的,或者该工具箱应该足够聪明,可以将构造函数从设计器文件移至用户一个文件,并且如果已经在设计器中不重新生成它存在于用户文件中。

14.缺少Partial修饰符并不意味着该类型不是partial。


从技术上讲,在VB中,只应将一个类标记为“部分”。 这通常是(在GUI项目中)一个生成的文件。

怎么了 这样可以使用户文件保持美观和整洁,并且在生成自定义代码或将自定义代码添加到生成的代码之后,可以非常方便地包含该文件。 但是,建议最多一个类没有Partial修饰符,否则会发出警告。

15.在默认类中,公共访问级别适用于除字段以外的所有内容,在公共结构中也适用于字段


我对此有百感交集。 在C#中,默认情况下所有内容都是private的(欢呼,封装!),但是根据您经常声明的内容,有一个参数可以做:公共合同或实现细节。 属性和事件通常是供外部( public )使用的,并且除了public之外,不能访问操作员。 但是,我很少依赖默认的可访问性(除了像本文中的示例这样的演示之外)。

16. VB在调用基本构造函数之后初始化字段,而C#在调用基本构造函数之前初始化字段


您是否听说过在构造函数中发生的第一件事是对基类的构造函数的调用? 好吧,至少在C#中不是这样。 在C#中,在调用base() (显式或隐式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

在从事反射的日子里,我经常写这样的代码。 而且我隐约记得微软(Josh)之前的一位同事,他曾将我的代码翻译成C#,有时抱怨需要将我所有的初始化程序移植到构造函数中。 在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#字段初始化程序可以调用基类的成员,则该成员可以依赖于在基函数构造函数(尚未运行)中初始化的字段,并且结果几乎肯定是错误的,因此无法解决。

但是在VB中,基本构造函数已经解决了,因此您可以执行任何操作! 在相反的情况下,一切都有些复杂,因为从基类的初始化程序(或构造函数)调用Overridable成员可能导致在字段“初始化”之前进行访问。 但是只有您的实现知道这是否有问题。 在我的脚本中,这只是没有发生。 它们不依赖于实例的状态,但不能成为Shared成员,因为出于技术原因,您不能拥有任何语言的Shared Overridable成员,但本文不涉及其范围。 另外,在启动自定义初始化程序之前,已明确定义了字段发生了什么-它们使用默认值进行初始化,就像VB中的所有变量一样。 没什么好奇怪的

那为什么呢 实际上,我不知道我的脚本是否是最初的VB.NET团队在设计脚本时所考虑的。 就我而言,它才有效! , : 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 «». VB6 Quick Basic ( ) . QB, . , « », . GoTo, — , .

: Try , VB - await Catch Finally , , GoTo .

22. <>


, VB ( ) ( static ) ( ). , . Catch 3 . Try Catch , , , Try .

, VB.NET , . CLR VB . : , .

, C# , , «». VB.NET .

23.


, , C# « » ( definite assignment ). , , , « ». , ( ) , , . C/C++. , ! , . , , , — . , , , , , , , , . , BASIC , , «» , = Nothing , = 0 , = False ..

, ( flow analysis ) VB , .

, C# , , , . VB , , , . 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

, null-conditional C# VS2015 C# , VB ( ), , ; VB.NET .

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 {.X = 3, .Y = 5} 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 . object.ReferenceEquals , , , - dynamic C# ( ). == , . 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 . - , goto , break . VB break , Exit Select , , VB .

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

-1, -2, -3. x — « », - x , . , x . flow analysis API — ! ( «… … ?» )

怎么了 , , , , , #22. , , -, .

, VB C# ( control variables ) For Each VS2012 (?), - « ». 10000% , ( , VB , ). , VB For , . , . , VB For For Each , for foreach C#. , For VB - , , .

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 , .

, , , , , C, VB . - , VB , For i = a To b Step c ( , i> b ) ( , i <b ), c ? , , , 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 , .

VB , ( well-known name ), . , , Add, . C# , (. async / await ). , C# Roslyn () , .
分钟的广告。 15-16 - .NET- DotNext 2019 Piter . , . , . .

Source: https://habr.com/ru/post/zh-CN443684/


All Articles