
An exhaustive list of differences between VB.NET and C #. Part 2
- 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 .
Converting
Why? The reason VB prefers to use from
Conversions to and from the lines "True" and "False" are also supported (of course, case insensitive).
From a philosophical point of view, language refers to
Story Time: The Roslyn API has gone through many internal revisions. But in each of them for each language it has been allocated listing
Years later, during the next revision of the API, we completely removed the (
Later, we decided to rename this property to
Integral types have a range, so, for example,
GitHub source code
By default, in VB, this loop will throw an exception (since the last iteration of the loop goes abroad
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
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
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 ...
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.
I believe that technically this is also true for reference types, but yes:
Why? Because
In both VB and C # you can convert
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
Because no one except me remembers the syntax of a character literal in VB (and it shouldn't).
42. There are conversions between
For definiteness: these transformations create new objects, you do not get access to the internal structure
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
This works with prefixes base and makes possible a very convenient way to convert a hex (or octal) Input among:
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
WHAT?!?!?
After reading about all these additional transformations, are you surprised? In VB, conversions between
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,
A literal
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”:
GitHub source code
The reason is quite simple: you are comparing
GitHub source code
It’s clear how someone can assume that the type
GitHub source code
Again, here intuitively it seems that it
Nuance: A new expression has been added in C # 7.1
What can be done about this? There are several suggestions, but none have won yet:
I would like to hear your thoughts in the comments and / or on Twitter .
So to summarize:
VB continues to resist adding an expression
Something like this.
Offtopic (but related)
Here is an example very similar to the second above, but without type inference:
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
GitHub source code
This program will not compile. Instead, she reports an error in the expression
It is important to keep in mind that solving problems with
This program displays on the console "3":
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
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
48. is
In VB.NET, you cannot assign to
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:
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
Why? It can be argued that ordinary instance members can be accessed without explicitly qualifying them with
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
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.
There are several ways to interpret namespaces:
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
Therefore, if I imported a namespace in VB
In C #, this is not the case.
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 inside
But it turns out even worse because, although both VB and C # require that namespaces are always fully qualified in instructions
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
Therefore, in VB2015, we added a smart name resolution (Smart Name Resolution), in which if you imported
But this does not solve the problem of adding a link, which brings a new top-level namespace
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:
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.
Surprisingly rarely mentioned, but when initializing an array in VB with syntax,
This has always been the case in Microsoft BASIC languages since the advent of the instruction
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
55. VB array literals
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:
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:
Creating an array of untyped expressions:
Performing bitwise transformations (internal or custom):
And as the target type is derived not only from the array, but also from the
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.
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 .
There is no exact match between cast / convert operators between VB and C #.
VB
VB
Cast in C # -
Of the first two, it’s
In fact, everyone can use it
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.
See the specifications for the full table of operator priorities, but they are not the same for different languages, therefore it is
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!
Let's just talk about how + (addition) and & (concatenation) in VB are different from each other and from + in C #.
Between
VB
C #
Between string and types that do not overload + and &
VB
C #
Between numeric types:
VB
C #
Between String and Enum: VB Types
C #
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:
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.
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
61. ^ not really
That is, it is not just an alias for
Nuance: F # also has an overloaded exponentiation operator **, but when overloaded, the VB and F # operators generate different names:
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 (
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.
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
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:
But not:
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
GitHub source code
So technically s = "" in VB is a shorthand for
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
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
Fortunately, you can now get the same productivity with the operator
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
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
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.
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
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 #:
In VB (again specifically for nullable significant types) comparing null with any other value yields null . That is, instead of the usual
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 #
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
When operands are of type
When operands are of type
In other words, if the result
In C #, it is not allowed to apply logical operators with a short circuit (&& / ||) and without (& / |) to operands
Why does it matter?
Usually the behavior of VB is only surprising when someone writes code like this:
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
At this point, the VB developer usually says: “Well, I figured out how it works. I invert this " :
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:
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
And if you use the latter interpretation, NULL as “unknown value”, then all three-valued logic in VB makes sense:
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?
LINQ to SQL
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

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.
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.
I have seen this several times in the converted code. It’s easy to get used to the idea that the syntax
What the = operator means, comparison or assignment, has always been determined by context. In the context of an instruction (such as
67. Lambda
In C #, when writing a
There is no such ambiguity in VB.NET, as the returning
To illustrate my sensational headline, look at this example in VB:
GitHub source code
For two reasons, it will not compile in VB, but will be in C #. Firstly, the type
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
GitHub source code
You ask, why is this program crazy? When a variable X is declared, its type is
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
(Because after the previous one this one is shocking, but ...)
When you write
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
Why? No idea. But the behavior of VB is very consistent. For example, when you type
71-75. An operator
For instance:
Why? Ask Amanda Silver ( Amanda Silver ). But I can tell fortunes!
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:
GitHub source code
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. ).

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 .
Content
Hidden text
Conversions
- 34. Boolean transformations
- 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
- 36. Overflow check / negative underflow 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
- 37. Converting floating point numbers to integer types uses bank rounding (bakers rounding) rather than truncating
- 38. It is not a mistake to convert NotInheritable classes to / from interfaces that they do not implement at compile time
- 39. Trying to unpack (unbox) null into a meaningful type results in a default value of the type, not a NullReferenceException
- 40. Unboxing supports primitive type conversions
- 41. There are conversions between String and Char
- 42. There are conversions between String and Char array
- 43 and 44. Conversions from String to numeric and date types support literal syntax (typically)
- 45. NO conversions between Char and integer types
Expressions
- 46. Nothing <> null
- 47. Brackets not only affect parsing priority; they reclassify variables to values
- 48. Me is always classified as meaning - even in structures
- 49. Extension methods are available by a simple name.
- 50. Static imports will not merge method groups
- 51 and 52. Partial-name qualification & Smart-name resolution
- 53. Add methods of the collection initializer can be extension methods.
- 54. Creating an array uses an upper bound, not a size
- 55. VB array literals cf. magic is not the same as implicitly typed array creation expressions in C #
- 56. Fields of anonymous type can be mutable AND are mutable by default
- 57. Neither CType nor DirectCast are exactly type coercion in C #
- 58. The priority of some “equivalent” operators does not necessarily coincide.
- 59. String concatenation is different; + and & differ in the context of string concatenation; + in VB <> + in C #
- 60. Division works adequately: 3/2 = 1.5
- 61. ^ not quite Math.Pow
- 62. The = / <> operators are never equality / inequality of links
- 63. The = / <> operators for strings are different (and any other relation operators in this context)
- 64. Nullable significant types use three-valued logic (propagate null in relation operators)
- 65. Overloaded operators do not always correspond to 1: 1
- 66. Function () a = b is different from () => a = b
- 67. Async Function lambda will never be treated as async void lambda
- 68. Requests are real (more real) in VB
- 69 and 70. The As statement in the From request does not always call cast; as a bonus, the 'As' operator can invoke implicit user conversions
- 71–75. The Select statement is 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
- 76. CONCLUSION SHOULD ...
Conversions
34. Boolean transformations
Converting
Boolean True
to 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 True
to 1
, and that is how it is most often represented in IL
. In the opposite direction, any number other than 0
is converted to True
. Why? The reason VB prefers to use from
-1
to 1
is 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
, Or
and 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
SyntaxKind
that 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 Integer
andSyntaxKind
This 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
RawKind
and 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,
Byte
can represent values from 0 to 255. So, what happens when you add Byte
1 to Byte
255? 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
Console.WriteLine(i)
Next
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
, NaN
types Single
or Double
to Decimal
throw 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
Short
to Integer
, but what if you try to convert packaged Short
to Integer
? The VB Short
will 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 String
andChar
String
converted toChar
representing its first character.Char
converted in theString
only 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 String
and the arrayChar
String
converted to an arrayChar
of all its characters.- An array is
Char
converted toString
, 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
ParamArray
to 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 ParamArray
you can now convert an array to a string Char
and 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 Char
and integer types
WHAT?!?!?
After reading about all these additional transformations, are you surprised? In VB, conversions between
Char
and 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, AscW
and ChrW
, respectively.Expressions
46. Nothing <> null
A literal
Nothing
in 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:- Nothing takes on a significant type, and ...
- 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
Console.WriteLine("True")
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
i
is Integer?
( Nullable(Of Integer)
). But this is not so, because it Nothing
gets 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 Nothing
never has a type). Another way to look at this problem is with the following example:Module Program
Sub Main()
M(Nothing)
End Sub
Sub M(i As Integer)
Console.WriteLine("Non-nullable")
End Sub
Sub M(i As Integer?)
Console.WriteLine("Nullable")
End Sub
End Module
GitHub source code
Again, here intuitively it seems that it
Nothing
adds 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 Nothing
is 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
default
that matches Nothing
in VB. If you rewrite all three examples above in C # using default
instead 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
Nothing
converted to a significant type and it is notnull
in anullable
significant type. - Nicely deploy
Nothing
in0
,0.0
,ChrW(0)
,False
,#1/1/0001 12:00:00 AM#
orNew 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
Null
orNothing?
- 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 # onlynull
. - 2005 - C # adds
default(T)
(the default value of the typeT
), 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 closedNothing
. - 2017 - C # adds
default
(default value in context) as there are many scenarios in which an indication isT
redundant 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 aboutNothing
andNothing?
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
Nothing
deprecated for most scenarios. Nothing
andNull
will 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)
Console.WriteLine(i)
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)
Console.WriteLine(i)
Dim operation As Func(Of Integer, Integer, Integer) =
If(True,
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
If
that she cannot determine the type of expression when both expressions AddressOf
are explicitly intended to receive delegates Func(Of Integer, Integer, Integer)
. It is important to keep in mind that solving problems with
Nothing
not always null (counterintuitively), Nothing
not 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
M((i))
Console.WriteLine(i)
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 i
is 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 source.roslyn.io, the result is BoundParenthesized
present 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 Me
always 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 this
within a structure, and you can pass this
by 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()
Extension()
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.Console
at 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.Debug
both of which methods are WriteLine
always 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
, andSystem.Windows.Forms
- this is the neighbors who have a common prefix of the agreement, butSystem
does not containSystem.Windows
andSystem.Windows
does not containSystem.Windows.Forms
. - Namespaces, on the other hand, are similar to folders organized in a hierarchy, and can contain other namespaces and types. So it
System
containsWindows
, butWindows
containsForm
.
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
using
in C # it behaves according to the first. Therefore, if I imported a namespace in VB
System
, I can access any namespace inside System
without 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 System
does not add System.Threading
to 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 inside
System
, then inside this type you can refer to the namespace System.Threading
as 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
/ directivesusing
At 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
System
contains a namespace ComponentModel
and System.Windows.Forms
contains a namespace ComponentModel
? The meaning ComponentModel
becomes 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
System
and System.Windows.Forms
write ComponentModel
, then, by analogy with the Schrödinger cat, a quantum superposition is created from both realities, where you refer to System.ComponentModel
and where you refer toSystem.Windows.Forms.ComponentModel
until 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.PropertyChangedEventArgs
must mean System.ComponentModel.PropertyChangedEventArgs
because it System.Windows.Forms.ComponentModel.PropertyChangedEventArgs
does 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 namespace
Windows
to scope because top-level namespaces (absolute paths) always win partially defined (relative paths) for various reasons (including performance). Therefore, the use WinForms/WPF
and UWP
in 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 Byte
or 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 meanCType(New Integer() {1, 2, 3}, Short ())
, since it's impossible to convert an arrayInteger
to an arrayShort
.CType({1, 2, 3}, Short())
reclassifies the array literal toNew 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,
Long
inInteger
(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
CType
closest 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).CType
supports 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 Short
to 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 * 3
calculated as 5 in VB, and the "equivalent" expression in C # is 5 % 2 * 3
calculated 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
String
and primitive types: VB
- “1” + 1 = 2.0
- “1” & 1 = “11”
C #
- "1" + 1 == "11"
Between string and types that do not overload + and &
VB
- “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:
VB
- 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_Exponent
and op_Exponentiation
accordingly. Although F # is actually looking for a method Pow
by 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 Binary
or 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
Console.WriteLine("Empty")
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 Text
setting 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
String
was "". Thus, VB and its runtime philosophically interpret the default value for String
as an empty string. In fact, this is one of the main advantages of using VB string functions, such as Left
and Mid
, instead of methods String
. The runtime functions also treat null as an empty string. So Len(CStr(Nothing)) = 0
and 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 Compare
actually affects calls to string functions of the VB runtime, such as InStr.Replace
other 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 obj
is 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
bool
insteadbool?
In VB (again specifically for nullable significant types) comparing null with any other value yields null . That is, instead of the usual
Boolean
operator = returns Boolean?
, which can take values True
, False
or 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 Nothing
returns 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/AndAlso
and Or/OrElse
. When operands are of type
Integer?
(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.- FALSE AND NULL will be FALSE
- 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/False
can 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 AndAlso
also OrElse
works 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?
Get
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
EndDate
checks 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?
LINQ to SQL
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
() => expression
in C # always matches the syntax Function() expression
in 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 = b
will always produce a delegate that compares a
and b
(returns Boolean
) rather than assigns b
to а
. 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 = b
from 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
Sub
lambda) it means assignment, in the context of an expression (such as Function
lambda) it means comparison.67. Lambda Async Function
will never be interpreted as a lambdaasync void
In C #, when writing a
async
lambda expression whose body does not return a value, it is syntactically ambiguous whether the lambda should return Task
or 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 Async
lambda uses the syntax Async Sub
, and the returning Task
or Task(Of T)
- uses the syntax Async Function
. However, there is another situation that can arise in VB when the Task Async
lambda to a void
delegate 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
Foo
does 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 Select
to eliminate this error, the last statement will not compile now Select
, because it Integer
cannot 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 Select
discarded). 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 let
declares 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 X
and Y
with the same names as our range variables, but they are both the type int
and completely unrelated to X
and Y
"declared" in the request. So when you refer to a variable Y
in an instruction select
, it has a type int
and 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 As
in a request From
does 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 y
in VB, this is not quite the same as from int x in y
in 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()
.Cast
.Cast
.Select
respectively. 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 T
may cause any permitted conversion or not call anything at all. So the behavior of range variables From
and operators As
is consistent with loops For Each
(and indeed, with all operators As
).71-75. An operator Select
is 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 > 10
permissible. Let's just say thatSelect
implicit.From x In y Select x Where x > 10
absolutely normal.From x In y Select x
actually the same asFrom x In y Select x = x
where,x
on the left is the new range variable with the namex
, andx
on the right is the range variable in the scope beforeSelect
. After theSelect
old oneх
goes out of scope.From x In y Select z = x.ToString()
, nowx
leaves completely.From x In y Select x.FirstName
actually the same thing as writingFrom x In y Select FirstName = x.FirstName
.From x In y Select x.FirstName, x.LastName
- it's likeFrom 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 thingIEnumerable(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
Select
implicitly 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 .