An exhaustive list of differences between VB.NET and C #. Part 2

Original author: Anthony D. Green
  • Transfer
In the first part of the article, the topic of VB.NET superiority over C # in terms of TIOBE rating found a lively response in the comments. Therefore, on the advice of AngReload, let's look at the trends of StackOverflow.

C # is still strong! The revolution that was talked about so long last time is canceled! Hooray, comrades! Or not? The TIOBE rating is based on search engine queries, and the SO rating is based on tags from questions asked. Perhaps the developers of VB.NET, which include many people who don’t have specialties, simply do not know about the existence of StackOverflow? Or having got there through Google, or even Bing, do not understand how to ask a question? Or maybe they have enough Miscrosoft documentation, and all the few questions have already been answered.

One way or another, the share of VB.NET is noticeable and stable, albeit not in the first place in terms of volume. And, of course, such a result would not have been possible without a strong team of designers and language developers. Below is the second part of the translation of an article by a member of this team, Anthony Green .


Hidden text




34. Boolean transformations

Converting Boolean Trueto any signed numerical type yields -1, and to any unsigned, the maximum value for this type, while in C # such conversions do not exist. However, a method Convert.ToInt32, for example, converts Trueto 1, and that is how it is most often represented in IL. In the opposite direction, any number other than 0is converted to True.

Why? The reason VB prefers to use from -1to 1is that the bitwise negation of 0 (all bits are set to 0) is equal in any language -1(all bits are set to 1), so using this value combines logical and bitwise operations, such as And, Orand Xor.
Conversions to and from the lines "True" and "False" are also supported (of course, case insensitive).

35. Conversions between Enum types as well as between Enum types and their base types are completely unlimited, even if Option Strict is set to On

From a philosophical point of view, language refers to Enum-types rather as a set of named constants of a basic integer type. The place where this is most obvious is equality. It is always permissible to compare any integer with an enumeration value, whereas in C # this gives an error.

Story Time: The Roslyn API has gone through many internal revisions. But in each of them for each language it has been allocated listing SyntaxKindthat tells you what kind of syntax is a node (for example IfStatement, TryCastExpression). Once, a developer used an API that tried to abstract from the language and returned one of the values SyntaxKind, but only how Integer, and without receiving errors when comparing raw IntegerandSyntaxKindThis developer immediately came to my office and complained: «int - this is an implementation detail, I had to make do cast!" .

Years later, during the next revision of the API, we completely removed the ( Property Kind As SyntaxKind) properties that pointed to a language-specific type, and all the APIs began to return Integer. All C # code broke, and all VB code continued to work as if nothing had happened.

Later, we decided to rename this property to RawKindand add language-specific extension methods Kind(). All C # code broke because parentheses were needed to call methods, but since they were not needed in VB, all VB code continued to work again as if nothing had happened.

36. The check for overflow / negative overflow for integer arithmetic is completely controlled by the compilation environment (project settings), but VB and C # use different default values; in VB, overflow checking is enabled by default

Integral types have a range, so, for example, Bytecan represent values ​​from 0 to 255. So, what happens when you add Byte1 to Byte255? If the overflow / underflow check is disabled, the value scrolls to 0. If the type is signed, it scrolls to the lowest negative number (for example, -128 for SByte). This most likely indicates an error in your program. If overflow / underflow checking is enabled, an exception is thrown. To understand what I mean, take a look at this harmless cycle For.

Module Program
    Sub Main()
        For i As Byte = 0 To 255
    End Sub
End Module

GitHub source code

By default, in VB, this loop will throw an exception (since the last iteration of the loop goes abroad Byte. But with the overflow check turned off, it loops because after 255 i again becomes 0.
Underflow is the opposite situation when the subtraction is below the minimum for the type of value leads to a maximum value.

a more common situation is for overflow -. it's just adding two numbers Take the number 130 and 150, both like Byte. If you add them up, the answer is 280, that does not fit in your CPU Byte But perceived. is it is not. Instead, he says that the answer is 24.

By the way, this has nothing to do with conversions. Adding two bytes gives a byte; this is just the way binary math works. Although you can also get overflow by performing a conversion, for example, when trying to convert Long to Integer. Without checking for overflow, the program simply cuts off the extra bits and stuffs as much as it fits into this variable.

What is the difference? Performance. Checking the CLR for overflow requires a bit more computational time compared to the non-checking option, like all other security checks. VB is based on the philosophy that developer productivity is more important than computational performance, so by default you have security checking enabled. C # development team can todaymake another decision on the default settings of the project, but if you consider that the first C # developers came from C / C ++ developers, this group of people would probably require that the code do nothing more, which could cost processor cycles; this is a difficult philosophical difference .

Caveat: even if the check overflow / underflow disabled, conversion values PositiveInfinity, NegativeInfinity, NaNtypes Singleor Doubleto Decimalthrow an exception, as none of these values can not in principle be presented to Decimal.

37. Converting floating point numbers to integer types uses bankers rounding rather than truncating

If you convert 1.7 to an integer in VB, the result will be 2. In C #, the result will be 1. I can’t say anything about mathematical rules outside of America, but when I switch from a real number to an integer, I instinctively round. And none of those I know outside the circle of programmers believes that the nearest integer to 1.7 is 1.

There are actually several rounding methods, and the type of rounding used in VB (and in the Math.Round method) is called bank rounding or rounding off statisticians by default. Its essence is that for a number in the middle between two integers, VB rounds to the nearest even number. So 1.5 is rounded to 2, and 4.5 is rounded to 4. What actually doesn’t work the way we were taught at school - I was taught to round up from 0.5 (technically, round to the side from zero). But, as the name implies, bank rounding has the advantage that with a large number of calculations, you divide when rounding in half, and do not always give out or always keep money. In other words, on a large set, this limits the data distortion to the ultimate statistical deviation.

Where does the difference come from?Rounding is more intuitive and practical, truncating faster. If you consider using VB in LOB applications, and especially in applications such as Excel macros that run on VBA, simply dropping decimal places can cause ... problems.

I think it is obvious that the method of conversion is always a controversial issue and should be indicated explicitly, but if you need to choose a single one ...

38. It is not a mistake to convert NotInheritable classes to / from interfaces that they do not implement at compile time

Generally speaking, if you are checking a NonInheritable class for an interface implementation, you can understand at compile time whether such a conversion is possible because you know all the interfaces of this type and all its basic types. If the type is inherited, you cannot be sure that such a conversion is impossible, because the type of the runtime object referenced may in fact have a more derived type that implements this interface. However, there is an exception due to COM interop, when at compile time it may not be visible that the type has anything to do with the interface, but at runtime it will. For this reason, the VB compiler generates a warning in such cases.

Why?VB and COM grew up together when they were kids in the old neighborhood. So there are several solutions in language design in which VB pays great attention to things that existed only in COM at the time of the release of .NET 1.0.

39. Trying to unpack (unbox) null into a meaningful type results in a default value of the type, not a NullReferenceException

I believe that technically this is also true for reference types, but yes:
CInt(CObj(Nothing)) = 0

Why? Because CInt(Nothing) = 0, and the language tends to be somewhat consistent, regardless of whether you typed your variables or not. This applies to any structure, not just built-in meaningful types. See the rationale at # 25 for more details.

40. Unboxing supports primitive type conversions

In both VB and C # you can convert Shortto Integer, but what if you try to convert packaged Shortto Integer? The VB Shortwill be first unpacked and then converted to Integer. In C #, unless you manually unzip short before converting to int, it will be thrown InvalidCastException.
This applies to all internal conversions, i.e. packed numeric types, conversions between strings and numeric types, strings and dates (yes, Decimal and Date are primitive types).

Why? Again, to ensure consistent behavior, whether your program is fully typed, typed as Object, or being refactored from one option to another. See # 39 above.

41. There are transformations between StringandChar

  • Stringconverted to Charrepresenting its first character.
  • Charconverted in the Stringonly reasonable way.

Because no one except me remembers the syntax of a character literal in VB (and it shouldn't).

42. There are conversions between Stringand the arrayChar

  • Stringconverted to an array Charof all its characters.
  • An array is Charconverted to String, consisting of all its elements.

For definiteness: these transformations create new objects, you do not get access to the internal structure String.

A funny story: I once found (or maybe reported it, and I researched) breaking change between .NET 3.5 and 4.0, because between these versions the .NET team added a modifier ParamArrayto the second overload parameter String.Join, which takes an array of strings. The exact assumptions are lost in time (probably for the better), but, as I believe, the reason is that with the modifier ParamArrayyou can now convert an array to a string Charand pass it as a separate element to the parameter array. Fun theme.

43 and 44. Conversions from String to numeric and date types support literal syntax (typically)

  • CInt("&HFF") = 255
  • CInt("1e6") = 1_000_000
  • CDate("#12/31/1999#") = #12/31/1999#

This works with prefixes base and makes possible a very convenient way to convert a hex (or octal) Input among: CInt("&H" & input). Unfortunately, this symmetry is degrading at the time of this writing, because the VB runtime has not been updated to support the binary & B prefix or digit group separator 1_000, but I hope this is fixed in the next version. Scientific notation works, but without the suffix types, as well as the conversion date support the standard date formats, so the JSON format used in the ISO-8601, also works: CDate("2012-03-19T07: 22Z") = #3/19/2012 02:22:00 AM#.

Why? I know no other reason than convenience. But I would reallyI also wanted to offer support for other common formats that are almost ubiquitous on the network today, such as #FF, U + FF, 0xFF. I think this could greatly facilitate life in some types of applications ...

45. NO conversions between Charand integer types

After reading about all these additional transformations, are you surprised? In VB, conversions between Charand are not allowed Integer:

  • CInt("A"c) not compiled.
  • CChar(1) not compiled.

Why? It is not clear what is going to happen. Usually VB in such situations uses a pragmatic and / or intuitive approach, but for expression, CInt("1"с)I think half of the readers would expect the number 1 (character value 1), and half would expect the number 49 (ASCII / UTF code for character 1). Instead of half the time to make the wrong choice, VB has special features for character conversion to ASCII / Unicode codes and back, AscWand ChrW, respectively.


46. ​​Nothing <> null

A literal Nothingin VB does not mean null . It means "the default value for the type it is used for," and it just so happens that for reference types, the default value is null. The difference only matters when used in a context in which:

  1. Nothing takes on a significant type, and ...
  2. It is not clear from the context that he is doing this.

Let's look at a few examples that illustrate what this means.

The first, perhaps a little strange, but I don’t think that most people will be blown up by the understanding that this program will print “True”:

Module Program
    Sub Main()
        Dim i As Integer = 0
        If i = Nothing Then
        End If
    End Sub
End Module

GitHub source code

The reason is quite simple: you are comparing Integer (0)with the default value of its type (also 0). The problem occurs in VB2005 / 2008 when you add nullable meaningful types. Take a look at this example:

 Module Program
    Sub Main()
        Dim i = If(False, 1, Nothing)
    End Sub
End Module

GitHub source code

It’s clear how someone can assume that the type iis Integer?( Nullable(Of Integer)). But this is not so, because it Nothinggets the type from the context, and the only type in this context comes from the second operand, and it is simple non-nullable Integer(technically Nothingnever has a type). Another way to look at this problem is with the following example:

Module Program
    Sub Main()
    End Sub
    Sub M(i As Integer)
    End Sub
    Sub M(i As Integer?)
    End Sub
End Module

GitHub source code

Again, here intuitively it seems that it Nothingadds a “nullable” hint and that the language selects an overload that accepts nullable, but it doesn’t (selects non-nullable, because it is “most specific”). At a minimum, it can be assumed that, like null in C #, the expression Nothingis not applicable at all Integer, and that nullable overloading will be chosen by the exception method, but again this is based on the wrong idea that Nothing = null (Is null?).

Nuance: A new expression has been added in C # 7.1 defaultthat matches Nothingin VB. If you rewrite all three examples above in C # using defaultinstead null, you get exactly the same behavior .

What can be done about this? There are several suggestions, but none have won yet:

  • Show a warning every time it is Nothingconverted to a significant type and it is not nullin a nullablesignificant type.
  • Nicely deploy Nothingin 0, 0.0, ChrW(0), False, #1/1/0001 12:00:00 AM#or New T(default value for any structure) whenever its value would be in the runtime of the above.
  • Add a new syntax meaning "Null, no, really!", Like NullorNothing?
  • Add a new syntax in the form of a suffix (?), Which wraps the value in nullable to help deduce the type, for example If(False, 0?, Nothing)
  • Add nullable conversion operators for built-in types to make it easier to give hints to type inference, e.g. If (False, CInt? (0), Nothing)

I would like to hear your thoughts in the comments and / or on Twitter .

So to summarize:

  • Former times - VB6 and VBA have "Nothing", "Null", "Empty" and "Missing", meaning different things.
  • 2002 - there is only in VB.NET Nothing(the default value in a specific context), but in C # only null.
  • 2005 - C # adds default(T)(the default value of the type T), because newly added generics create a situation where you need to initialize the value, but you do not know if it is a reference type or significant; VB does nothing because this script is already closed Nothing.
  • 2017 - C # adds default(default value in context) as there are many scenarios in which an indication is Tredundant or impossible

VB continues to resist adding an expression Null(or equivalent) because:

  • The syntax will be breaking change.
  • The syntax will not be breaking change, but depending on the context will mean different things.
  • The syntax will be too inconspicuous (for example, Nothing?); Imagine you need to talk out loud about Nothingand Nothing?to explain something to a person.
  • The syntax may be too ugly (e.g. Nothing?).
  • The null expression script is already closed Nothing, and this function will be completely redundant most of the time.
  • Everywhere, all documentation and all instructions should be updated to recommend using the new syntax, which is mostly Nothingdeprecated for most scenarios.
  • Nothingand Nullwill still behave the same in runtime with respect to late binding, transformations, etc.
  • It can be like a gun in a stabbing.

Something like this.

Offtopic (but related)

Here is an example very similar to the second above, but without type inference:

Module Program
    Sub Main()
        Dim i As Integer? = If(False, 1, Nothing)
    End Sub
End Module

GitHub source code

This program displays 0. It behaves exactly the same as the second example, for the same reason, but illustrates a separate, albeit related, problem. Intuitively, it Dim i as Integer? = If(False, 1, Nothing)behaves the same as Dim i As Integer? : If False Then i = 1 Else i = Nothing. In this case, this is not so, because the conditional expression (If)does not “flow through” information of a finite type to its operands. It turns out that it breaks all the expressions in VB, which rely on what is called the final (PPC) typing ( Nothing, AddressOf, array literals, lambda expressions, and interpolated line), with problems ranging from nekompiliruemosti do to quiet the creation of incorrect values, and to loud ejection exceptions. Here is an example of an uncompiled option:

Module Program
    Sub Main()
        Dim i As Integer? = If(False, 1, Nothing)
        Dim operation As Func(Of Integer, Integer, Integer) =
               AddressOf Add,
               AddressOf Subtract)
    End Sub
    Function Add(left As Integer, right As Integer) As Integer
        Return left + right
    End Function
    Function Subtract(left As Integer, right As Integer) As Integer
        Return left - right
    End Function
End Module

GitHub source code

This program will not compile. Instead, she reports an error in the expression Ifthat she cannot determine the type of expression when both expressions AddressOfare explicitly intended to receive delegates Func(Of Integer, Integer, Integer).

It is important to keep in mind that solving problems with Nothingnot always null (counterintuitively), Nothingnot pointing to nullability(counterintuitively) and If(,,)not providing context for intuitive behavior Nothing(and other expressions) (counterintuitively) is all separate problems, and solving one will NOT solve others.

47. Brackets not only affect parsing priority; they reclassify variables to values

This program displays on the console "3":

Module Program
    Sub Main()
        Dim i As Integer = 3
    End Sub
    Sub M(ByRef variable As Integer)
        variable = -variable
    End Sub
End Module

GitHub source code

A similar program in C # will produce “-3”. The reason is that in VB, taking a variable in brackets makes it behave like a value - a process known as reclassification . At this point, the program behaves as if you had written M(3), and not M(i), and no reference to the variable iis passed, so it cannot be changed. In C #, bracketing an expression (for any reason) will not make it a value instead of a variable, so calling M will change the original variable.

Why? VB has always had this behavior. In fact, I just opened my copy of Quick Basic (Copyright 1985), and there the behavior is the same. Given that the transfer by referenceused by default until 2002, all this obviously makes sense.

Caveat # 1: “How the Subprogram Got Parentheses” by Paul Wick, Honored Visual Basic .NET Architect
Caveat # 2: When we designed the linked tree in Roslyn compilers (a data structure representing the semantics of the program (the meaning of things) as opposed to the syntax (the form of writing things)), this became a stumbling block for the compiler team: will the expressions in parentheses be represented in the linked a tree. In C #, parentheses are an almost completely syntactic construct used to control parsing priority((a + b) * c или a + (b * c)). So much so that the original C # compiler, written in C ++, discarded the fact that the expression was enclosed in brackets, along with things like spaces and comments. There were several attempts at reconciling between the languages: “Can we look and get rid of them in VB?” Or “Can we live with them in C #?” And ultimately, according to, the result is BoundParenthesizedpresent in the VB compiler and missing in the C # compiler. In other words, the languages ​​here are different, and we just have to accept it.

48. is Mealways classified as meaning - even in structures

In VB.NET, you cannot assign to Me. Usually this is not surprising, but someone might think that since structures are just a set of values, it is permissible to assign an instance of Structure of type Structure to Me as a simplified copy record. However, this is also forbidden and when transferring Me by reference, a copy will be simply transferred. In C #, it is permissible to assign in thiswithin a structure, and you can pass thisby reference inside the methods of an instance of the structure.

49. Extension methods are available by a simple name.

In VB, if an extension method is defined for a type and it is in the scope of the definition of this type, then inside the definition of this type you can call this method by an unqualified name:

Class C
    Sub M()
    End Sub
End Class
Module Program
    Sub Main()
    End Sub
    Sub Extension(c As C)
    End Sub
End Module

GitHub source code

In C #, extension methods are only looked up when explicitly specifying the recipient (i.e. something.Extension). So, although the exact translation of the example above will not compile in C #, you can access the extensions in the current instance by explicitly specifying this.Extension().

Why? It can be argued that ordinary instance members can be accessed without explicitly qualifying them with 'Me.', and since extension methods behave everywhere as instance members, it is logical that they behave in this context as well. VB.NET adheres to this argument. Presumably there are other arguments, and other languages ​​are free to stick to them.

50. Static imports will not merge method groups

VB has always supported “static imports” (a Java term that combines the C # modifier with the VB statement). This is what allows me to write Imports System.Consoleat the top of the file and use WriteLine()without qualification in the rest of the file. In 2015, C # also got this opportunity. However, in the import situation VB two types Shared-members with the same name, for example System.Console, and System.Diagnostics.Debugboth of which methods are WriteLinealways treated as ambiguous. C # will unite the groups of methods and perform overload resolution, and if there is a definite result, then it will be selected.

Why?I think we can assume that here VB could be smarter than C # (especially considering the following difference). But there is also an argument that if two methods come from two different places and generally have nothing to do with each other (one is not an extension method for a type defining the other), then this ... is misleading when all options are offered under one and by the same name.

Moreover, there are many cases in VB with the same scenario when VB chooses a more secure path and reports ambiguity, for example, two methods with the same name from unrelated interfaces, two methods with the same name from different modules, two methods from different hierarchy levels inheritance, where one is not an explicit overload of the other ( difference # 6) VB is philosophically consistent here. In addition, VB made all of these decisions in 2002.

51 and 52. Partial-name qualification & Smart-name resolution

There are several ways to interpret namespaces:

  • On the one hand, all namespaces are neighbors in a flat list and contain only types (but not other namespaces). So System, and System.Windows.Forms- this is the neighbors who have a common prefix of the agreement, but Systemdoes not contain System.Windowsand System.Windowsdoes not contain System.Windows.Forms.
  • Namespaces, on the other hand, are similar to folders organized in a hierarchy, and can contain other namespaces and types. So it Systemcontains Windows, but Windowscontains Form.

The first model, in particular, is useful for displaying namespaces in a graphical interface without a deep nesting. However, the second one was always intuitively closer to me. And VB with its Imports directive follows the second model, while usingin C # it behaves according to the first.

Therefore, if I imported a namespace in VB System, I can access any namespace inside Systemwithout qualifying it with System. For me, it's like specifying a relative path. So in all my examples where I qualify ExtensionAttribute, I write instead .

In C #, this is not the case. using Systemdoes not add System.Threadingto scope under a simple name Threading.

But it turns out even better, because C # allows a partial qualification script similar to the relative path specifically in the case when the code is defined in this namespace. That is, if you declare a type insideSystem , then inside this type you can refer to the namespace System.Threadingas Threading. And this is consistent behavior, because you can declare a namespace and a type that are lexically contained in another namespace, and it would be strange if a search by name inside a type would not find a neighbor.

But it turns out even worse because, although both VB and C # require that namespaces are always fully qualified in instructions Imports/ directivesusingAt the file level, C # allows you to have a using directive inside a namespace declaration, affecting the code inside this declaration in this file, and in such directives using namespaces can be specified using a simple name.

The advent of Quantum Namespaces (unofficial name)

But wait, there's more! The VB is comfortable, but this is risky. After all, what happens if it Systemcontains a namespace ComponentModeland System.Windows.Formscontains a namespace ComponentModel? The meaning ComponentModelbecomes ambiguous. And sometimes it turns out that you can just write in the code ComponentModel.PropertyChangedEventArgs, and everything will be fine (I vaguely recall that earlier versions of the designer did this in the generated code). But then you importSystem.Windows.Forms(or maybe just add a reference to the assembly that declares a subspace of names with that name in the one you imported), all your code will break with ambiguity errors.

Therefore, in VB2015, we added a smart name resolution (Smart Name Resolution), in which if you imported Systemand System.Windows.Formswrite ComponentModel, then, by analogy with the Schrödinger cat, a quantum superposition is created from both realities, where you refer to System.ComponentModeland where you refer toSystem.Windows.Forms.ComponentModeluntil you enter another identifier. If this identifier is a child namespace in both realities, the wave continues until there is an identifier that uniquely indicates a type that exists in only one temporal universe. At this moment, the wave collapses, and it turns out that the cat was always dead, i.e. ComponentModel.PropertyChangedEventArgsmust mean
System.ComponentModel.PropertyChangedEventArgsbecause it System.Windows.Forms.ComponentModel.PropertyChangedEventArgsdoes not exist. This avoids many of the ambiguities that may arise simply when importing a new namespace.

But this does not solve the problem of adding a link, which brings a new top-level namespaceWindowsto scope because top-level namespaces (absolute paths) always win partially defined (relative paths) for various reasons (including performance). Therefore, the use WinForms/WPFand UWPin the same project can still be painful.

53. Add methods of the collection initializer can be extension methods.

As mentioned in # 33 , when VB searches for something, it usually considers extension methods as well. The scenario when you might need this is to use a brief initializer syntax for collections of complex objects, for example:

Class Contact
    Property Name As String
    Property FavoriteFood As String
End Class
Module Program
    Sub Main()
        Dim contacts = New List(Of Contact) From {
                           {"Leo", "Chocolate"},
                           {"Donnie", "Bananas"},
                           {"Raph", "The Blood of his Enemies"},
                           {"Mikey", "Pizza"}
    End Sub
    Sub Add(collection As ICollection(Of Contact), name As String, favoriteFood As String)
        collection.Add(New Contact With {.Name = name, .FavoriteFood = favoriteFood})
    End Sub
End Module

GitHub source code

Initially, C # did not consider extension methods in this context, but when we re-implemented the collection initializers in the Roslyn C # compiler, they began to take them into account. It was a bug that we decided not to fix (and not a feature that we decided to add), so this difference is relevant only until VS2015.

54. Creating an array uses an upper bound, not a size

Surprisingly rarely mentioned, but when initializing an array in VB with syntax, Dim buffer(expression) As Byteor the Dim buffer = New Byte(expression) {}size of the array is always equal expression + 1.

This has always been the case in Microsoft BASIC languages ​​since the advent of the instruction DIM(meaning dimension). Which, I suppose, explains why this works: the size of the array is from 0 to expression. In previous versions of Microsoft BASIC languages, it was possible to change the lower bound of arrays by default to 1 (and it was possible to declare an array with an arbitrary lower bound, for example 1984), in this case the upper bound coincided with the length (I usually did), but this possibility disappeared in 2002.

But if you dig deeper, I heard about the once fashionable trend in language design to make the declaration syntax simulate the use syntax, which explains why arrays in VB are declared with their upper boundary, why in BASIC and in C the boundaries of arrays are pointed to a variable, and not on type, pointer syntax in C, why types are on the left in C-like languages. Think about it, any use buffer(10)will use a value from 0 to 10, not up to 9!

55. VB array literals cf. magic is not the same as implicitly typed array creation expressions in C #

Although these two functions are often used in the same scenarios, they are not the same. The main difference is that the literals of the VB array are not typified in nature (like lambdas) and get the type from the context, and in the absence of context, from the expressions of their elements. The specification illustrates this well:

  • CType({1, 2, 3}, Short())doesn't mean CType(New Integer() {1, 2, 3}, Short ()), since it's impossible to convert an array Integerto an array Short.
  • CType({1, 2, 3}, Short())reclassifies the array literal to New Short() {1, 2, 3}. There is no spoon.

This is actually pretty cool because it means that things can happen to a VB array literal that are not possible with an implicitly typed array in C #. For example, passing empty:

  • Dim empty As Integer() = {}

Creating an array of untyped expressions:

  • Dim array As Predicate(Of Char)() = {AddressOf Char.IsUpper, AddressOf Char.IsLower, AddressOf Char.IsWhitespace}

Performing bitwise transformations (internal or custom):

  • Dim byteOrderMark As Byte() = {&HEF, &HBB, &HBF} 'Не нужны байтовые литералы.

And as the target type is derived not only from the array, but also from the IList(Of T), IReadOnlyList(Of T), ICollection(Of T), IReadOnlyCollection(Of T)and IEnumerable(Of T), you can very concisely convey a variable number of arguments to a method that accepts one of these types, which makes it unnecessary ParamArray IEnumerable.

Why?Prior to writing this document, I thought that this difference basically boils down to extra effort on the part of VB. But now I think that everything is much simpler. Before local type inference appeared in 2008, VB and C # allowed you to initialize an array declaration using the syntax "set" {}, but you could not use this syntax anywhere else in the language (except, I think, attributes). What we now consider array literals is really just a generalization of what this syntax can do with any context expression + a few other subtleties, such as output from the above generalized interfaces. Which is very elegant.

56. Fields of anonymous type can be AND are mutable

This does not affect anonymous type fields created implicitly using LINQ. But what you create can be mutable or immutable, it all depends on you.

Details on why and how are here .

57. Neither CType nor DirectCast are exactly type coercion in C #

There is no exact match between cast / convert operators between VB and C #.
VB CType:

  • Supports custom conversions;
  • Supports referenced conversions (base class to derived);
  • Supports internal transformations, for example, Longin Integer(see the section "Transformations");
  • Unpacks (unboxes) complex meaningful types directly;
  • DOES NOT decompress primitive types directly;
  • DOES NOT support dynamic conversions (use function CTypeDynamic).

VB DirectCast:

  • DOES NOT support custom conversions;
  • Supports link conversions;
  • DOES NOT support internal conversions (cannot convert Integer в Byte);
  • Unpacks complex meaningful types directly;
  • Unpacks primitive types directly (hence the name);
  • DOES NOT support dynamic conversions.

Cast in C # - (Type)expression:

  • Supports custom conversions;
  • Supports link conversions;
  • Supports internal conversions;
  • Unpacks complex meaningful types directly;
  • Unpacks primitive types directly;
  • Supports dynamic conversions.

Of the first two, it’s CTypeclosest to a cast in C # in the sense that it can be used in a wider set of scripts. In fact, in terms of language, this is a conversion operator. But VB and C # allow and prohibit different transformations, have different semantics for the same transformations, and in some cases generate different IL for these transformations. So there is no way to get exactly the same set of transformations as in C #, with exactly the same semantics and exactly the same code generated in all cases using the same operator. And it should not be.

In fact, everyone can use it CType, with the exception of dynamic transformations (transformations that look for a custom transform operator at runtime).CTypesupports all the scripts that it supports DirectCast, and even more. And always, when both of them can be used, they will generate the same IL with one exception: when converting from Object(or ValueType) to a primitive type, instead of generating the CLR "unbox" instruction, the compiler generates a call to the VB runtime function, which will complete successfully. if the type of the object is the target type OR if the value of the object can be converted to this type (for example, by extension Shortto Integer). This means that it will succeed more often than in C #. But in this case only internal conversions between primitive types are supported. In an extremely narrow set of scenarios, this can make a difference, but in the vast majority of cases not.

Why?Languages ​​support various conversions. Transformation operators must support transformations that support languages, not transformations that support other languages, unless there is a special reason.

58. The priority of some “equivalent” operators does not necessarily coincide.

See the specifications for the full table of operator priorities, but they are not the same for different languages, therefore it is 5 Mod 2 * 3calculated as 5 in VB, and the "equivalent" expression in C # is 5 % 2 * 3calculated as 3.

Operator priority is probably the oldest part of any language family. I noticed this only when I examined the effect of operators that exist in only one language (for example, integer division (\) in VB), on operators after it, which otherwise might be at the same level, but it seems the differences are much more common . You are warned!

59. String concatenation is different; + and & differ in the context of string concatenation; + in VB <> + in C #

Let's just talk about how + (addition) and & (concatenation) in VB are different from each other and from + in C #.
Between Stringand primitive types:

  • “1” + 1 = 2.0
  • “1” & 1 = “11”

C #

  • "1" + 1 == "11"

Between string and types that do not overload + and &

  • “Obj:“ + AppDomain.CurrentDomain 'Error: + not defined for String and AppDomain.
  • ”Obj:“ & AppDomain.CurrentDomain 'Error: & not defined for String and AppDomain.
  • ”Obj:“ + CObj (AppDomain.CurrentDomain) 'Exception, no + operator found.
  • ”Obj:“ & CObj (AppDomain.CurrentDomain) 'Exception, no & operator found.

C #

  • "Obj:" + AppDomain.CurrentDomain == "obj:" + AppDomain.CurrentDomain.ToString ()
  • "Obj:" + (object) AppDomain.CurrentDomain == "obj:" + AppDomain.CurrentDomain.ToString ()
  • "Obj:" + (dynamic) AppDomain.CurrentDomain == "obj:" + AppDomain.CurrentDomain.ToString ()

Between numeric types:

  • 1 + 1 = 2
  • 1 & 1 = “11”

C #

  • 1 + 1 == 2

Between String and Enum: VB Types

  • “Today:” + DayOfWeek.Monday 'Exception: String “Today:” cannot be converted to Double.
  • “Today:” & DayOfWeek.Monday = “Today: 1”
  • “Today:” & DayOfWeek.Monday.ToString () = “Today: Monday”

C #

  • "Today:" + DayOfWeek.Monday == "Today: Monday"

Callus: I really don’t like that + is basically allowed to be used to concatenate strings in VB, it remained due to backward compatibility. + always concatenates strings, but its current behavior is more like a bug than anything else. Why? Because:

  • “10” - “1” = 9.0,
  • “5” * “5” = 25.0,
  • “1” << “3” = 8, and
  • “1” + 1 = 2.0, but
  • “1” + “1” = “11”

Any other arithmetic operator converts strings to numbers. Inconsistency is a design bug.

Bottom line: do not use +, because it looks like in other languages. To get the behavior you need, use &, because this highlighted operator exists to uniquely indicate an intention ( concatenation , not addition). Also, be careful when concatenating enumeration values; they behave in this context as their numeric values.

60. Division works adequately: 3/2 = 1.5

From time to time I conduct an experiment - I go up to a random person and ask him: “How many will be three divided by two?” . Most people say one and a half. Only the most ideologized of us glance at me and say: “It depends. What are the types of the three and the two? ”
That is the whole difference between VB and C #.

If you want C-style behavior, which I suppose is the answer to the question “How many times does 5 integer in 9?” , Use the integer division operator \. Another argument, I believe, is that division is closed on a set of integers, except division by 0 (which would matter if the interface ever came up INumeric).

61. ^ not really Math.Pow

That is, it is not just an alias for Math.Pow. This is an overloaded operator that must be explicitly moved out of the scope of primitive types. It saddens me how often user ( custom) numeric types do not support it (I'm talking about you, System.Numerics.BigInteger).

Nuance: F # also has an overloaded exponentiation operator **, but when overloaded, the VB and F # operators generate different names: op_Exponentand op_Exponentiationaccordingly. Although F # is actually looking for a method Powby type of operand. That is, these languages ​​interact poorly with each other. A sad fact that I would like to see once corrected.

62. The = / <> operators are never equality / inequality of links

In C #, '==' sometimes uses the (overloaded) equality operator, sometimes language equality, and sometimes equality of references (if the types of operands do not overload the equality and are an object or interface). In VB, this statement will never mean equality of links. VB has separate operators ( Is/IsNot) for equality of references.

Story Time:at some point in the history of Roslyn, we had a class hierarchy that overloaded equality in value. In fact, we had two such hierarchies. Once we decided to abstract from both using a hierarchy of interfaces. All VB code properly broke when switching to using interfaces, because = ceased to be valid, but a bug was formed on the C # side, because most of the code that had previously used overloaded equality by value silently began to use the more stringent requirement of link equality.

63. The = / <> operators for strings are different (and any other relation operators in this context)

String equality in VB differs in several aspects.

First, whether binary string comparisons (case-sensitive) or culture-dependent (case-insensitive) are used, it depends on whether the file or project level is set to Option Compare Binaryor Option Compare Text. Option Compare Binary, by the way, is the default value for all projects in VS.

This setting governs all explicit and implicit string comparisons (but not character comparisons) that occur in the language, but do not affect most API calls. I.e:

  • Equality / Inequality: “A” = “a” / “A” <> “a”
  • Ratio: “A”> “a”
  • Operators Select Case: Select Case “A” : Case “a”

But not:

  • Challenges Equals: “A”.Equals(“a”)
  • Challenges Contains: ”A”.Contains(“a”)
  • Query operator Distinct: From s In {“A”, “a”} Distinct

But there is another, much more significant difference that may surprise you: in VB, zero and empty lines are considered equal. Thus, regardless of the setting Option Compare, this program will output “Empty”.

Module Program
    Sub Main()
        Dim s As String = Nothing
        If s = "" Then
        End If
    End Sub
End Module

GitHub source code

So technically s = "" in VB is a shorthand for String.IsNullOrEmpty(s).
Practically speaking, this distinction does not mislead people as often as you might think, because the operations that can be performed on the null and empty lines are almost the same. You will never call the members of an empty string, because you know all the answers in advance, and concatenation treats zero strings as empty.

Why? I consider the Option Compare Textsetting for backward compatibility, but I understand why it even appeared. There are many situations where you want case-insensitive comparisons of strings.
In fact, in most cases when I use strings, I want them to be case insensitive.

In fact, in all cases except passwords and encryption keys. I do not want my lazy reluctance to print to literally affect my results. Yes, I'm the kind of monster that uses case insensitive collation in SQL Server because I value my productivity. And when you consider that the history of VB includes not only VB6, but also VBA for Office products, such as Excel and Access, and VBScript for Windows, and that web browser that once ... no one had time for case sensitive. However, I accept that the .NET API is generally case sensitive and I do not use Option Compare Text because it only affects the language level. If there was a setting that would affect all .NET APIs, I would cut this infection and never return to it again.

As for null, seen as an empty string, I have a theory. There were no null rows in VB6. The default value for Stringwas "". Thus, VB and its runtime philosophically interpret the default value for Stringas an empty string. In fact, this is one of the main advantages of using VB string functions, such as Leftand Mid, instead of methods String. The runtime functions also treat null as an empty string. So Len(CStr(Nothing)) = 0and Left(CStr(Nothing), 5) = ""as well CStr(Nothing).Length, or CStr(Nothing).Trim()just fall.

Fortunately, you can now get the same productivity with the operator ?. (at least in terms of not throwing exceptions).

Why is it important:
For me, the main problem is that this difference is present everywhere where two string values ​​are compared in the language, that is, any string comparison in any expression. Including query expressions! The way to compare VB strings is that every time you type "String A" = "String B", it turns into a callMicrosoft.VisualBasic.CompilerServices.Operators.CompareString, and when the string comparison in the query expression or lambda expression is converted to the expression tree, it is displayed in the tree not as an equality check, but as a call to this function. And invariably, every new LINQ provider throws an exception when this node is detected. They simply do not expect such a pattern, because their libraries have not been tested with VB (or have not been tested well enough). This usually means that support for this library is delayed until someone can explain to them how to recognize this pattern. This happened with LINQ-to-SQL, LINQ-to-Entities, and some others that I encountered while working at Microsoft. Everything looks great until the VB developer compares the two lines, then BOOM!

So besides the fact that the semantics of string comparisons are slightly different from C #, this creates real problems for VB users using LINQ with new providers. Correction options: 1) change the way that VB generates expression trees to an obvious lie, or 2) change the way that VB generates equality, using a pattern that will be easier for LINQ providers to recognize. I like the latter, although it requires refinement of the VB runtime (probably).

Nuance: note that I said "most API calls." Because it Option Compareactually affects calls to string functions of the VB runtime, such as InStr.Replaceother members of the module Microsoft.VisualBasic.Strings. You may ask how the compilation setting affects the operation of an already compiled library function?

Well, you know how the compiler can pass the current file name or line number as a value for some optional parameters, if they are correctly formatted? It turns out that before this function was added, the same scheme was used for string functions: the compiler passes a value indicating the setting at this point in the program through an optional parameter.

64. Nullable significant types use three-valued logic (propagate null in relation operators)

VB and C # handle nullable differently. In particular, in the field of null propagation.

If you work a lot with SQL, you are probably very familiar with null propagation. In short, this is the idea that if you take a certain operator (for example, +), and if one or more of its operands is null, then the result of the whole operation is also null. This is similar to the operator "?.": if the expression obj?.Property objis null, then the whole expression as a result throws null, not an exception.

When using nullable significant types with unary and binary operators, both VB and C # propagate zeros. But they behave differently in a key area: relationship operators.

In VB, specifically in the case of nullable significant types, if either operand is null, the whole expression is null with two exceptions. So 1 + null will be null and null + null will be null. But this applies not only to arithmetic operations, but also to relation operators (for example, = and <>) and here lies the difference with C #:

  • All operators of the VB relation, except Is/IsNot, returnBoolean?
  • All C # relation operators (==,! =,>, <,> =, <=) Return boolinsteadbool?

In VB (again specifically for nullable significant types) comparing null with any other value yields null . That is, instead of the usual Booleanoperator = returns Boolean?, which can take values True, Falseor null. This is called three-digit logic . In C #, the result of a comparison is always non-nullable bool, which is accordingly a two-valued logic .

Please note that I said any value. This includes null itself. So in VB, NULL = NULL is NULL , not TRUE.
So, a couple of funny consequences of the corresponding design options:

It broke my brain.Null is no more than itself, but equal to itself, and at the same time no more or equal to itself in C # .

And this is the essence of the problem. If you use the VB model in C #, the most natural way to ask the question “Is this null?” In C # (if (value == null))will fall every time. There is no such problem in VB, because VB has separate operators for equality by value (= / <>) and equality by reference (Is/IsNot), so the idiomatic way of checking for a zero value in VB Is Nothingreturns the usual one non-nullable Boolean.

Earlier, I mentioned an exception to the rule that in VB the whole expression is null if one of the operands is null. This exception applies to And/AndAlsoand Or/OrElse.

When operands are of typeInteger? (or other integral), and VB and C # propagate null, as you would expect:

  • 1 AND NULL will be NULL
  • 1 OR NULL will be NULL

When operands are of type Boolean?, in VB, things get more complicated.

  • TRUE OR NULL will be TRUE
  • TRUE AND NULL will be NULL
  • FALSE OR NULL will be NULL

In other words, if the result True/Falsecan be uniquely calculated based on one operand, the result will be this value, even if the other operand is null. It also means that the short circuit of the logical operators AndAlsoalso OrElseworks as expected.

In C #, it is not allowed to apply logical operators with a short circuit (&& / ||) and without (& / |) to operands nullable boolean (bool?). Which is not as problematic as I thought at first, because all relation operators generate a non-nullable boolean and the nullable boolean operand has little chance of getting into the expression anyway.

Why does it matter?
Usually the behavior of VB is only surprising when someone writes code like this:

Imports System.ComponentModel
Class BindableRange
    Implements INotifyPropertyChanged
    Property _EndDate As Date?
    Property EndDate As Date?
            Return _EndDate
        End Get
        Set(value As Date?)
            ' This line here:
            If value = _EndDate Then Return
            _EndDate = value
            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(NameOf(EndDate)))
        End Set
    End Property
    Public Event PropertyChanged As PropertyChangedEventHandler _
        Implements INotifyPropertyChanged.PropertyChanged
End Class
Module Program
    WithEvents Range As New BindableRange
    Sub Main()
        Range.EndDate = Today
        Range.EndDate = Today
        Range.EndDate = Nothing
        Range.EndDate = Nothing
    End Sub
    Private Sub BindableRange_PropertyChanged(sender As Object, e As PropertyChangedEventArgs) _
        Handles Range.PropertyChanged
        Console.WriteLine(e.PropertyName & " changed.")
    End Sub
End Module

GitHub source code

You will probably be surprised to know that this program outputs “EndDate change” three times instead of two. Remember, I said that in VB, null is not equal to itself ? Since it never equals itself when the property setter EndDatechecks to see if the new value matches the old value, the test fails a second time when the code assigns the property a value Nothing.

At this point, the VB developer usually says: “Well, I figured out how it works. I invert this " :
If value <> _EndDate Then
    _EndDate = value
    RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(NameOf(EndDate)))
End If

But that doesn't work either! In fact, now in the code the event will not be generated at all. Instead, it will only be generated if the value changes from one non-nullable value to another . Because zero is neither equal nor unequal to itself . To fix the error, you need to write like this:
If value Is Nothing AndAlso _EndDate Is Nothing Then Return
If value <> _EndDate Then Return

Where does this difference come from and how is it better?
I mentioned why the C # team decided to follow the path of their design, and that these problems do not apply to VB at all. When thinking about null values ​​(both in nullable meaningful types and in reference types), I often see a conflict between two concepts about what null is.

One way to look at null is in the meaning of "nothing" or "does not exist . " This is what is usually meant for reference types.

In other cases, null means “not defined” or “unknown”. This is often what is meant by optional parameters that were not provided: it is not that the caller suggests you not use comparer, but it suits you if you use comparer by default. And if you look at the Roslyn code base, then in fact there is a type with a name Optional(Of T)that is used to describe this concept for reference types, because otherwise it is impossible to distinguish values ​​that should be null from values ​​that simply weren't are provided.

And if you use the latter interpretation, NULL as “unknown value”, then all three-valued logic in VB makes sense:

  • If I ask you, “three is greater than the unknown value?” You can only answer “I don’t know.”
  • Аналогично: «У меня есть две коробки с неизвестными предметами, это одни и те же предметы?» «я не знаю».

And that is probably why this interpretation is used by default in SQL databases . By default, if you try to compare NULL in SQL with any value, you will get NULL in response. And this is especially true when working with data. Everyone who reads this post now will have NULL in the Date of death column. This does not mean that we will all die in one day. If several people fill out the form, most will not fill out their middle name, although they may (this is optional). This does not mean that all these people have the same middle name, although some legally do not have a middle name and you can say that the middle name is an empty string, but you understand how NULL is open to interpretation, especially in SQL databases (with exceptions).

Which brings us back to VB.What was the coolest scenario for nullable significant types in 2008 when their full universal support was added to VB?

The VB model provides consistency between the database from which these types probably come from and the language, as well as between comparisons when they appear in a LINQ query and when they are executed on the server. For me it is extremely convincing!

But there is a catch. In SQL Server, at least there is the SET ANSI_NULLS OFF option , which makes SQL expressions behave more like in C #, so you can write WHERE Column = NULL. And, I admit, in the past I usually set it to OFF (along with setting the base sort to be case insensitive). So I turned to the SQL Server team (a few years ago) for help. I asked:“What is the meaning of this option? I use her. Is this the right way, and whether we need to add something like Option ANSI_NULLS Off in VB.NET? » . Their answer is generally reflected in the documentation for the option:

In short, this option is needed for backward compatibility, it may well disappear in the future, and they would like all people using SQL Server to adapt to the current VB approach.
Something like this.

65. Overloaded operators do not always correspond to 1: 1

There are cases when VB supports two representations of an operator that would be uniform in other languages, for example, regular and integer division. In these cases, operator overloading in VB can quietly overload other operators used in other languages.

Similarly, there are cases where other languages ​​overload certain operators separately, for example, logical and bitwise negation or signed and unsigned bitwise offset. In these cases, VB can recognize such overloads defined in other languages, if they are the only available option, and in cases where both options are available, VB can ignore one option completely.

Section 9.8.4 of the specification is an exhaustive list of these correspondences.

66. Function () a = b is different from () => a = b

I have seen this several times in the converted code. It’s easy to get used to the idea that the syntax () => expressionin C # always matches the syntax Function() expressionin VB. However, lambda Function()is only for lambda expressions that return something, which is not the case for assignment in VB. Using this syntax with the body of the view a = bwill always produce a delegate that compares aand b(returns Boolean) rather than assigns bto а. However, due to VB's delegate relaxation, this lambda can still be safely (and quietly) passed to the Sub-delegate (which does not return a value). In this case, the code just silently does nothing. The correct translation () => a = bfrom C # to VB is this Sub() a = b. This code is a lambda instruction, which correctly contains the assignment statement and can be used for its side effects.

What the = operator means, comparison or assignment, has always been determined by context. In the context of an instruction (such as Sublambda) it means assignment, in the context of an expression (such as Functionlambda) it means comparison.

67. Lambda Async Functionwill never be interpreted as a lambdaasync void

In C #, when writing a asynclambda expression whose body does not return a value, it is syntactically ambiguous whether the lambda should return Taskor void, therefore, in overload resolution there is a rule to use the option that returns Task, if any.

There is no such ambiguity in VB.NET, as the returning void Asynclambda uses the syntax Async Sub, and the returning Taskor Task(Of T)- uses the syntax Async Function. However, there is another situation that can arise in VB when the Task Asynclambda to a voiddelegate is relaxed by discarding its return value. This lambda wo n't behave likeAsync Sub, so a warning has been added for the case when such a simplification occurs.

68. Requests are real (more real) in VB

To illustrate my sensational headline, look at this example in VB:

Class Foo
    'Function [Select](Of T)(selector As Func(Of String, T)) As Foo
    '    Return Me
    'End Function
    Function Where(predicate As Func(Of String, Boolean)) As Integer
        Return 0
    End Function
End Class
Module Program
    Sub Main()
        Dim c As New Foo
        Dim q = From x In c Where True Select x
    End Sub
End Module

GitHub source code

For two reasons, it will not compile in VB, but will be in C #. Firstly, the type Foodoes not have a method Select, and therefore it cannot be used for queries, so it is not even allowed to use the operator on it Where. But if you uncomment the definition Selectto eliminate this error, the last statement will not compile now Select, because it Integercannot be used for queries. However, in C #, translation is done syntactically in such a way that the entire request is reduced to a simple call .Where(the latter is Selectdiscarded). Since it does not use all the written query constructs, it does not throw errors when the pattern is broken.

This difference only manifests itself in language design or when trying to represent LINQ in an API. But the way queries are designed in VB and C # is different. In particular, C # queries are modeled on the idea that they are just a “syntactic conversion”, which means that the language specification defines them in terms of translating the query syntax into another syntax, and all semantic analysis happens AFTER the translation is completed. In a sense, this means that the language is “detached” from what things can mean in the middle, and does not imply any guarantees.

On the other hand, in VB, the language carefully describes the operatorsqueries with sufficiently strict semantics and may require that objects adhere to certain restrictions at intermediate stages, which C # would apply only after the final conversion or not apply at all if the translation does not require them.

Examples of questions we had to ask at Roslyn include: “Are there range variables?” And “Are there types of range variables?” . The answer is slightly different depending on the language in question. For example, in VB you can explicitly set the types of variables declared in the query statement Let, but in C # you ca n’t. But let me give you an example of a program whose equivalent does not compile in any version of VB, but compiles in C # 2012 despite the fact that it is Crazy:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CSharpExamples
    struct Point
        public int X { get { return 0; } }
        public int Y { get { return 0; } }
    class Foo
        public IEnumerable Select(Func selector)
            return new Point[0];
    static class Program
        static void Main(string[] args)
            var c = new Foo();
            var q = from X in c let Y = "" select Y.CompareTo(-1);

GitHub source code

You ask, why is this program crazy? When a variable X is declared, its type is string. Then the instruction letdeclares a new range variable Y, which is also of type string. But the underlying query statement does not create a sequence of anonymous types, it actually creates a sequence of Point types that just has properties Xand Ywith the same names as our range variables, but they are both the type intand completely unrelated to Xand Y"declared" in the request. So when you refer to a variable Yin an instruction select, it has a type intand int`a members , and it just ... compiles.

That's what I mean when I say"Is there a variable range and whether or not they have types? ' . Prior to VS2015 in C #, it can be argued that the answer is no. However, at Roslyn we actually tightened the rules a bit in C #, and this program will no longer compile. Thinking about these two examples brought me quite a headache, so no matter how monstrous the examples are (and I’m sure that there are), let someone else's brain produce them.

Why?This is a compromise between the simplicity and elegance of the function description in the specification for users and its implementation in the form of a simple syntactic transformation, as well as the specifics of the experience you want to create and all the work associated with it. I can’t say that there is a right and wrong approach for any situation, the development teams of VB and C # and the languages ​​themselves simply have different precedents and principles here.

69 and 70. An operator Asin a request Fromdoes not always call cast; as a bonus, an operator 'As'can invoke implicit user conversions

(Because after the previous one this one is shocking, but ...)

When you write From x As Integer In yin VB, this is not quite the same as from int x in yin C #.

First, in C #, specifying a type here always means that you are casting (or converting) the original collection. A call will be made . In VB, it can be just a stylistic choice to avoid type inference, and therefore, if the specified type is a type of a collection item, no conversion is performed. Secondly, you are not limited to transformations that can be performed statically by a method (known as reference transformations). You can use here any type into which you can convert the original type, including through user-defined conversions, and the request will be translated into a call or.Cast()


Why? No idea. But the behavior of VB is very consistent. For example, when you type For Each x As T In collection, a part As Tmay cause any permitted conversion or not call anything at all. So the behavior of range variables Fromand operators Asis consistent with loops For Each(and indeed, with all operators As).

71-75. An operator Selectis not required at all, it can appear in the middle of a request, it can appear several times, and it can declare several range variables with implicit or explicit names

For instance:

  • From x In y Where x > 10permissible. Let's just say that Selectimplicit.
  • From x In y Select x Where x > 10 absolutely normal.
  • From x In y Select xactually the same as From x In y Select x = xwhere, xon the left is the new range variable with the name x, and xon the right is the range variable in the scope before Select. After the Selectold one хgoes out of scope.
  • From x In y Select z = x.ToString(), now xleaves completely.
  • From x In y Select x.FirstNameactually the same thing as writing From x In y Select FirstName = x.FirstName.
  • From x In y Select x.FirstName, x.LastName- it's like From x In y Select New With {x.FirstName, y.LastName}, except that in scope there are no range variables. But from the point of view of the result of the entire query expression, they produce the same thing IEnumerable(Of $AnonymousType$), therefore, it is almost never necessary to explicitly create an anonymous type.

Why? Ask Amanda Silver ( Amanda Silver ). But I can tell fortunes!

  • Я предполагаю, что Select может идти где угодно, потому что с точки зрения SQL уже раздражает, что Select не идет первым, а так вы можете поставить его вторым. В изначальном предложении по дизайну LINQ в VB была попытка дать вам возможность ставить Select первым, как в SQL, но с точки зрения инструментария поставить первым From было наилучшим вариантом.
  • Я предполагаю, что вы можете его опустить, потому что нет причин требовать обратного.
  • Я предполагаю, что вы можете сделать Select для нескольких выражений, потому что вы можете сделать это в SQL, и нет никакого смысла делать это как-то по-другому и не нужно использовать явно синтаксис анонимных типов. Также в VB имеется большой прецедент допустимости comma separated (разделенных запятой) списков чего-либо.
  • I assume that it Selectimplicitly declares the names, because if it weren’t, you would have to repeat the names, and you need the names, because if there were no names, you would have nothing to refer to in the following instructions, and the projection of the subsets columns from database tables is an extremely common scenario.

Does it matter? These differences, in my opinion, are important due to an error that is sometimes issued and which is difficult to understand in situations like this:

Module Program
    Sub Main()
        Dim numbers = {1, 2, 3}
        ' BC36606: Range variable name cannot match the name of a member of the 'Object' class.
        Dim q = From n In numbers
                Select n.ToString()
    End Sub
End Module

GitHub source code

BC36606: Range variable name cannot match the name of a member of the 'Object' class and BC30978: Range variable '…' hides a variable in an enclosing block or a range variable previously defined in the query expression- both can result from an unintentional declaration of a range variable with the same name as a member Object, or a local variable in scope outside the request, necessarily in the scenario when the request selects one value that is supposed to be anonymous. The problem is solved by enclosing the expression ( n.ToString()) in brackets , because this prevents implicit naming. I would like the language to stop reporting this error once in this common case.

76+. Method call and overload resolution are different

I tried to fit all this into one page. I ... didn’t have enough ... strength. I will publish ... the remaining 20-25 differences next week ( at the time of translation, the author did not keep his promise - approx. Per. ).

Minute of advertising. On May 15-16, a conference for .NET developers of DotNext 2019 Piter will be held in St. Petersburg . There will be many reports regarding the details of the work and the internal structure of the platform. The program is still in its infancy, but about half of the reports are already known. On the official website you can familiarize yourself with the program and purchase tickets .

Also popular now: