
According
to the TIOBE
rating in 2018, VB.NET overtook C # in popularity. Coincidence or not, but in February Eric Lippert, one of the creators of C #,
urged readers to pay attention to the blog of his friend, a former colleague of the Roslyn compiler and, concurrently, an ardent fan of VB.NET,
Anthony Green . “Such resources are in-depth details from experts who are not so easy to find while reading the documentation,” writes Eric. We present to your attention the first part of the translation of the article by Anthony Green “An exhaustive list of differences between VB.NET and C #”. Perhaps it is in these differences lies the secret of the dynamics of the rating of these languages.
For almost half my life, I witnessed and participated in countless discussions about how similar the two most popular .NET languages ​​are or differ. First as an amateur, then as a professional and, finally, as a client advocate (customer advocate), a program manager and a language designer, I can say without exaggeration how many times I have heard or read something like:
“... VB.NET is actually just a thin layer on top of IL, like C # ...”
or
"... VB.NET is actually just C # without semicolons ..."
As if languages ​​were an XML transformation or style sheet.
And if a certain ardent visitor does not write this in the comments, then this is often implied in questions like:
“Hi, Anthony! I ran into such a small difference in one place — is it a bug? How could these two otherwise identical languages, which should be identical for the sake of all the good and holy in this world, disperse in this one place? Why do we need such injustice ?!')
“
Break up, ” as if they were the same until a mutation occurred, and
then became separate species. HA!
But I understand that. Before I joined Microsoft, I might also vaguely stick to this idea and use it as an argument to respond to opponents or to reassure someone. I understand her charm. It is easy to understand and very easy to repeat. But working on Roslyn (essentially rewriting VB and C # completely from scratch) for 5 years, I realized how
uniquely false this idea is . I worked with a team of developers and testers to re-implement every inch of both languages, as well as their tools in a huge multi-project solution with millions of lines of code written in both languages. And given the large number of developers switching back and forth between them, and the high level of compatibility with the results and experience of previous versions, as well as the need to reliably reproduce the enormous amount of API in the smallest details, I was forced to very closely become familiar with the differences. In fact, sometimes it seemed to me that I learned something new about VB.NET (my favorite language) every day.
And finally, I took the time to sit down and unload from the brain a particle of what I learned, using and creating VB.NET over the past 15 years, in the hope that I can at least save my time next time.
Before turning to the list, I will set out the basic rules:
- This list is not exhaustive in the usual sense. He is an exhaustive force. These are not all existing differences. It's not even all the differences that I generally know. These are just the differences that I can remember first, before I get too tired to continue; until i'm running out of power. If I or some of you meet or remember other differences, I’ll be happy to update this list.
- I will start from the beginning of the VB 11 specification and move down using its content to remind myself of the differences that come to mind first on this topic.
- This is NOT a list of functions in VB that are not in C #. So no "XML literals vs. pointers." This is too trite, and there are already tons of such lists on the Internet (some of which were written by me, and perhaps in the future I will write more). I will focus primarily on constructs that have an analogue in both languages, and where an ignorant observer may assume that these two things behave in the same way, but where there are small or large differences; they may look the same, but work differently or ultimately generate different code.
- This is NOT a list of syntactic differences between VB and C # (of which there are countless). I will mainly talk about semantic differences (what things mean), and not about syntax differences (how things are written). So no “VB starts comments with ', and C # uses //” or “in C # _ is a valid identifier, but not in VB”. But I’ll break this rule for a few occasions. In the end, the first section of the specification is devoted to lexical rules.
- Quite often I will give examples, and sometimes I will offer justifications why the design could go one way or another. Some design decisions were made before my eyes, but the vast majority preceded my time, and I can only guess why they were taken.
- Please leave a comment or tweet me ( @ThatVBGuy ) to let me know your favorite differences and / or those you would like to know more deeply about.
Deciding on expectations and without further delay ...
Content
Hidden textSyntax and preprocessing
Announcements, etc.
Instructions
Syntax and preprocessing
1. Keywords and VB operators can use full-width characters.
In some languages ​​(I don’t know how many exactly, but at least in some forms of Chinese, Japanese and Korean) full-width characters are used. In short, this means that when using a monospaced font (as most programmers do), the Chinese character will take up twice the horizontal space than the Latin characters that we used to see in the west. For example:

Here I have a declaration of a variable written in Japanese, and initialization with a string, also written in Japanese. According to the Bing translator, the variable is called “greeting”, and the string says “Hello World!”. The name of a variable in Japanese is only 2 characters, but it occupies a space of 4 characters half-width, which my keyboard usually gives out, as the first comment shows. There are full-width versions of numbers and all other printed ASCII characters that have the same width as the Japanese. To demonstrate this, I wrote a second comment using full-width numbers "1" and "2". These are not such "1" and "2", as in the first comment. There are no spaces between the numbers. You can also see that the size of the characters - not exactly 2 characters wide, there is a slight offset. This is partly because this program mixes full-width and half-width characters in one line and in all three lines.
Spaces are half width, alphanumeric characters are full width. We are not programmers, if not obsessed with text alignment. And it seems to me that if you are Chinese, Japanese or Korean (or someone else who uses full-sized characters for their language) and use identifiers or strings written in their own language, these minor alignment errors are infuriating.
As far as I understand, depending on your Japanese keyboard, switching between hieroglyphs and Latin is easy, but it is preferable to use full-width Latin characters. VB supports this in keywords, spaces, operators and even quotes. So all this can be written like this:

As you can see, in this version, keywords, spaces, comments, operators, even quotes use their full-size versions. Order is brought into chaos.
Yes. The Japanese use VB. In fact, despite the syntax that is similar to English (and maybe that is why), for the majority of VB users, whom I see on the forums, English is not the main language. During my time at Microsoft, I met the Japanese VB MVP several times, at least one of them brought Japanese sweets all the time. If you are a VB programmer from China, Japan or Korea (or from any other country that uses full-width characters), please write in the comments. (In the comments the author wrote that the Japanese are trying to use ascii everywhere in the code -
Approx. Trans. )
Funny moment: When I originally implemented interpolated strings in VB, I (to my disgrace) did not take into account the possibility of full-width curly braces at the placeholders. Vladimir Reshetnikov ( @vreshetnikov ) discovered and corrected this error, so the great tradition of VB tolerance to the width of characters remained in force.2. VB supports smart quotes
Well, it is, of course, a trifle, but worthy of mention. Have you ever seen sample code in a text document like this:

And after copying the example into your code, did you find that none of the (highlighted) quotes work, because Word replaces all the usual ASCII quotes with
“
smart quotes
”
?
Me not. Well, I had this, but only when I copied the examples in C #. In VB, smart quotes are valid delimiters for strings (funny that Russian quotes
«»
do not work - Approx. Trans.):

They also work within lines, although perhaps in a strange way. If you double smart quotes for screening, all you get at runtime is just a regular (“stupid”) quote. This may seem a bit strange, but nevertheless it is very practical, since almost everywhere there are no more smart quotes allowed in the string. The compiler does NOT force you to end up with a smart quotation mark or use the right one if you started with a smart one, so you can mix it up and not bother. And yes, it also works with the single quote character used for comments:

I tried to get Paul Wick (
@panopticoncntrl ) to admit that he did this solely because he was tortured with this problem while working on the specification, but he denies the blame. This was not the case in VB6, so someone added this later.
3. The preprocessing constants can be of any primitive type (including dates) and can contain any constant value.

4. Arbitrary arithmetic operators can be used in preprocessing expressions.

Announcements, etc.
5. VB sometimes skips implements declarations in IL to prevent accidental implicit interface implementations by name.
This item is from the category of esoteric. In VB, an interface implementation is always done explicitly. But it turns out that in the absence of an explicit implementation, the default behavior of the CLR when calling an interface method is to look for public methods by name and signature. In most cases this is normal, because in VB you
usually need to provide an implementation for each member of the interface that you implement, except for one case:
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()
gist.github.com/AnthonyDGreen/39634fd98a0cacc093719ab62d7ab1e6#file-partial-re-implementation-vbIn this example, the class
FooDerived
just wants to reassign
IFoo.Bar
to a new method, but leave the remaining implementations unchanged. It turns out that if the compiler simply generates the implements directive for
FooDerived
, the CLR will also catch
FooDerived.Baz
as a new implementation of
IFoo.Baz
(although in this example it is not related to
IFoo
). In C # this happens implicitly (and I’m not sure if it can be discarded), but in VB the compiler actually omits 'Implements' from the entire declaration to avoid it, and overrides only specific members that have been re-implemented. In other words, if you ask
FooDerived
if it
IFoo
directly, it will say no:

Why do I know this and why is it important? For years, VB users have requested support for implicit interface implementations (without explicitly stating
Implements
in each declaration), usually for code generation. Just turning it on with the current syntax would be breaking change, because
FooDerived.Baz
now implicitly implements
IFoo.Baz
, although it hasn’t done so before. But more recently, I learned about this behavior better when discussing potential problems in the design of the “default interface implementation” feature that would allow interfaces to include some members' default implementations and not require re-implementation in each class. This would be useful for overloads, for example, when the implementation is likely to be the same for all implementers (delegation of the main overload). Another scenario is versioning. If the interface can include default implementations, you can add new members to it without breaking the old implementation. But there is a problem. Because the CLR default behavior is to look for public implementations by name and signature, if the VB class does not implement interface members with default implementations, but has public members with a suitable name and signature, they implicitly implement these interface members, even if they do it completely was not supposed to. There are things that can be done to get around this when the full set of interface members is known at compile time. But in case a member was added after the code was compiled, it would just silently change the behavior at runtime.
6. VB by default hides the members of the base class by name (Shadows), not by name and signature (Overloads)
I think this distinction is quite well known. The scenario is this: you inherit a base class (
DomainObject
), perhaps beyond your control, and declare a method with a name that makes sense in the context of your class, for example,
Print
:
Class DomainObject End Class Class Invoice Inherits DomainObject Public Sub Print(copies As Integer)
gist.github.com/AnthonyDGreen/863cfd1e7536fe8bda7cd145795eaf9f#file-shadows-example-vbThat the invoice can be printed makes sense. But in the next version of the API, where your base class is declared, they decide to add a method to all DomainObjects for debugging, which displays the full contents of the object in the debugging window. This method is brilliantly called Print. The problem is that the client of your API may notice that the Invoice object has the methods
Print()
and
Print(Integer)
, and think that these are related overloads. Maybe the first one simply prints one copy. But this is not at all what you conceived as the author of Invoice. You had no idea what
DomainObject.Print
would appear. So yes, in VB it doesn't work that way. When this situation pops up, a warning appears, but more importantly, the default behavior in VB is to hide by name. That is, unless you explicitly specify using the
Overloads
keyword that your
Print
is the base class's
Print
overload, the base class member (and any of its overloads) is completely hidden. Only the API you initially declared is shown to clients of your class. This is how it works by default, but you can do it explicitly through the Shadows keyword. C # can only
Overloads
(although it takes
Shadows
into account when referencing the VB library) and does so by default (using the
new
keyword). But this distinction pops up from time to time, when some inheritance hierarchies appear in projects, where one class is defined in one language and another in another, and there are overloaded methods, but this is beyond the scope of the current item in the list of differences.
7. VB11 and below are more restrictive to Protected members in generics.
In fact, we changed it between VS2013 and VS2015. In particular, we decided not to bother with re-implementation. But I am writing this distinction in case you use the old version and notice it. In short: if a Protected member is declared in the generic type, the heir, being also a generic, can access this protected member only through the inherited instance with the same type arguments.
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))
gist.github.com/AnthonyDGreen/ce12ac986219eb51d6c85fa02c339a2f#file-protected-in-generics-vb8. The syntax "named argument (Named argument)" in attributes always initializes properties / fields
VB uses the same syntax
:=
to initialize attribute properties / fields as for passing method arguments by name. Therefore, there is no way to pass an argument to an attribute constructor by name.
9. All top-level declarations (usually) are implicitly in the root of the project namespace.
This difference is almost in the category “additional features”, but I included it in the list, because it changes the meaning of the code. In the properties of the VB project there is a field:

By default, this is just the name of your project at the time of creation. This is
NOT the same field as the “Default namespace” in the C # project properties. Default namespace simply specifies which code is added by default to new files in C #. But root namespace in VB means that, unless otherwise specified, every top-level declaration in this project is implicitly located in this namespace. This is why VB document templates usually do not contain any namespace declarations. Moreover, if you add a namespace declaration, it does not override the root, but is added to it:
Namespace Controllers
gist.github.com/AnthonyDGreen/fd1e5e3a58aee862a5082e1d2b078084#file-root-namespace-vbThus, the
Controllers
namespace actually declares the
VBExamples.Controllers
namespace, unless you get rid of this mechanism by explicitly declaring the
Global
namespace.
This is convenient because it saves one level of indentation and one unnecessary concept per VB file. This is especially useful if you are creating a UWP application (because everything in the UWP should be in a namespace), and it’s extremely convenient if you decide to change the top-level namespace for your entire project, say, from some code name like Roslyn to a longer release version like
Microsoft.CodeAnalysis
, since you don’t have to manually update each file in a solution. It is also important to keep this in mind when working with code generators, XAML namespaces and the new
.vbproj
file
.vbproj
.
10. Modules are not generated as sealed abstract classes in IL, so they are not exactly like static C # classes and vice versa.
Modules in VB existed before the static C # classes, although we tried in 2010 to make them identical in terms of IL. Unfortunately, it was breaking change, because the XML Serializer (or maybe it was binary) for that version of .NET (I think they fixed it) did not want to do serialization of a type nested in a type that cannot be created (and the abstract class can not). He threw an exception.
We discovered this after making changes and rolled them back, because some code somewhere used the enum type, which was nested in a module. And since you don’t know which version of the serializer the compiled program will work with, it will never change, because in one version of the application it will work, and in others it will throw exceptions.
11. You do not need an explicit method for the entry point (Sub Main) in WinForms applications.
If your project uses Form as a start object and does not use the “Application Framework” (more on this in the next post), VB generates
Sub Main
, which creates your start form and passes it to
Application.Run
, saving you either this way or a whole file to manage this process, either an additional method in your
Form
, or even the need to think about this problem.
12. If you call some obsolete VB runtime methods (for example, FileOpen), the calling method will be implicitly flagged with an attribute to disable inlayning for correctness reasons.
In short, methods for working with VB6-style files like
FileOpen
rely on the context specific to the assembly where the code is located. For example, file # 1 can be a log in one project and a config in another.
Assembly.GetCallingAssembly()
is called to determine which assembly is running. But if JIT embeds (
inlines ) your method into the caller, then from a stack point of view, the VB-runtime method will not be called by your method, but by the caller, which may be in a different assembly, which may then allow your code to access or break the internal state of the caller. object. This is not a security issue, because if the compromising code is running in your process, you have already lost. This is a question of correctness. Therefore, if you use these methods, the compiler disables inlining.
This change was made at the last moment in 2010, because the x64 JIT is VERY aggressive at inlining / optimizing code, and we found it very late, and it was the safest option.
13. If your type is marked with the DesignerGenerated attribute and does not contain any explicit constructor declarations, then the compiler-generated default will call InitializeComponent if it is defined for this type.
In the era before the emergence of Partial-types, the VB team waged war on reducing boilerplate code in WinForms projects. But even with
Partial
this is useful because it allows the generated file to completely omit the constructor, and the user can manually declare it in his file, if needed, or not declare it if not. Without this, the designer would have to add a constructor only to call
InitializeComponent
, and if the user adds too, they will be duplicates, or the toolkit must be smart enough to move the designer from the designer file to the user and not re-generate it in the designer, if it is already exists in the user file.
14. The absence of the Partial modifier does NOT mean that the type is not partial.
Technically, in VB only one class should be marked as Partial. This is usually (in GUI projects) generated file.
Why? This keeps the user file nice and clean, and can be very convenient for inclusion after generating or completing the generated code with user code. However, it is recommended that a maximum of one class not have a Partial modifier, otherwise a warning is issued.
15. In classes, the default access level is Public for everything except fields, and in Public structures for fields too
I have mixed feelings about this. In C #, everything is by default
private
(hooray, encapsulation!), But there is an argument to do depending on what you often declare: a public contract or implementation details. Properties and events, as a rule, are intended for external (
public
) use, and operators cannot be accessible otherwise than
public
. However, I rarely rely on default accessibility (except for demos like the examples from this article).
16. VB initializes fields AFTER the call to the base constructor, while C # initializes them BEFORE the call to the base constructor
Have you heard how “some” say that the first thing that happens in the constructor is a call to the constructor of the base class? Well, it is not, at least in C #. In C #, before calling
base()
, explicitly or implicitly, first the field initializers are executed, then the constructor call, and then your code. This decision has consequences, and I think I know why language developers could have taken this or that way. I consider one of these consequences - that the following code cannot be translated into C # directly:
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-vbAt the time when I was engaged in Reflection, I often wrote such code. And I vaguely recall a colleague to Microsoft (Josh), who translated my C # code, sometimes complaining about the need to transfer all my initializers to the constructor. In C #, it is forbidden to refer to the created object before
base()
is called. And since the field initializers are executed before the specified call, they also cannot refer to other fields or any members of the object instance. So this example also works only in VB:
MustInherit Class Base
gist.github.com/AnthonyDGreen/fe5ca89e5a98efee97ffee93aa684e50#file-base-derived-init-vbHere we have a base class, which presumably has a lot of functionality, but it needs some key object to manage, work, which is determined by the derived type. There are many ways to implement such a pattern, but I usually use this one because:
- he is short;
- does not require a constructor ad from me;
- It does not require me to put the initialization code in the constructor, if it has one;
- allows me to cache the created object and does not require derived types to declare and manage storage for the provided object, although now this is not a problem with automotive properties.
Further, I have been in both situations: when a field in a derived type wanted to call a method declared in the base class, and when the initializer of the base class field needed to call the
MustOverride
member implemented by the derived type. Both are valid in VB and none in C #, and that makes sense. If the initializer of the C # field could call a member of the base class, this member could depend on the fields initialized in the basic constructor (which is not yet running) and the results would almost certainly be wrong, and this would not work around.
But in VB, the base constructor has already worked out, so you can do anything! In the reverse situation, everything is a bit more complicated, because calling an
Overridable
member from the initializer (or constructor) of the base class can lead to access to the fields before they are “initialized”. But only your implementation knows if this is a problem. In my scenarios, this just does not happen. They are independent of the instance state, but cannot be
Shared
members, because you cannot have a
Shared Overridable
member in any language for technical reasons beyond the scope of this article. In addition, it is clearly defined what happens to the fields prior to the launch of user initializers - they are initialized with default values, like all variables in VB. No surprises.
So why? In fact, I don’t know if my scripts were what the original VB.NET team had in mind when they designed it. Just in my case, it really works! , : 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()
gist.github.com/AnthonyDGreen/57ce7962700c5498894ad417296f9066#file-read-only-auto-property-backing-field-is-writeable-vb20.
,
NonSerialized
.
VB (expanded) Custom- 2005 (?)
, , ,
NonSerialized
. , , , , «» , « ».
, , , , . , , , , two-way bindable ( ,
PropertyChanged
), , , , , .
, , CLSA «Expert Business Objects»
(Rocky Lhotka) , undo/redo ( , , - , , ), . , . , , , .
Instructions
21. — , ; ( )
,
GoTo
. , - . ,
For
For Each
;
Using
,
SyncLock
With
, , ,
Finally
.
If
Select Case
,
Do
While
, Try — , :
Module Program Sub Main() Dim retryCount = 0 Try Retry:
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
- — , .
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. , :
, , , , (
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
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-vbRoslyn 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 — ! (
«… … ?» )
Why? , , , , , #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-vbF# ,
IEnumerator
,
For Each
, .
VB , (
well-known name ), . , , Add, . C# , (.
async
/
await
). , C# Roslyn () , .
. 15-16 - .NET- DotNext 2019 Piter . , . , . .