For a given class, with a default property of list, you can access an instance object in the list by doing myClass.defProperty("key"). You can also achieve the same results by typing myClass.defProperty!Key.
I have been told that using the parenthesis and quotes is faster for the way the runtime accesses the Property, but I'd like to understand what is the difference and how do each work...
I understand C# has a similar behavior by replacing the parenthesis with square brackets.
Given the following code in Visual Basic.NET:
Dim x As New Dictionary(Of String, String)
x.Item("Foo") = "Bar"
You can access the "Foo" member of the dictionary using any of the following:
Dim a = x!Foo
Dim b = x("Foo")
Dim c = x.Item("Foo")
If you look at the IL under Reflector.NET then you'll find that they all translate to:
Dim a As String = x.Item("Foo")
Dim b As String = x.Item("Foo")
Dim c As String = x.Item("Foo")
So, they are all equivalent in IL and, of course, they all execute at the same speed.
The bang operator only lets you use statically defined keys that conform to the standard variable naming rules.
Using the indexed approaches then your keys can be almost any valid value (in this case string) and you can use variables to represent the key.
For code readability I would recommend the x.Item("Foo") notation as is is very clear as to what is going on. x("Foo") can be confused with a call to a procedure and x!Foo makes the Foo look like a variable and not a string (which it really is). Even the Stack Overflow color-coding makes the Foo look like a keyword!
The C# equivalent for this code is x["Foo"];. There is no ! syntax equivalent.
So, the bottom-line is that ! isn't better or worse on performance and just may make code maintenance more difficult so it should be avoided.
Related
In C# I can do this in a method:
var Content = new Dictionary<string, System.Text.StringBuilder>();
and in VB.Net I can do this in a method:
Dim Content = new Dictionary(of string, System.Text.StringBuilder)
However in VB I can also do this outside of a method when defining a field/member variable (note, in this case, VB is not inferring the type, it's getting it explicitly, this was in VB.Net prior to 'option infer'):
Dim Content AS new Dictionary(of string, System.Text.StringBuilder)
In C# it seems like I have to do it like this:
Dictionary<string, System.Text.StringBuilder> Content = new Dictionary<string, System.Text.StringBuilder>();
Notice I am having to repeat the type twice.
This is a simple example, for more complex cases it does get at least a bit annoying.
I can't believe something is more compact in VB; am I missing something here? How to I declare a member variable and initialize without repeating the type?
edit
From the Eric Lippert document mentioned in comments by Ron: "... If our goal is to remove the redundancy, I would therefore prefer to remove it the other way. Make this legal: private static readonly Dictionary niceNames = new()... That is, state the type unambiguously in the declaration and then have the "new" operator be smart about figuring out what type it is constructing based on what type it is being assigned to ..."
So there is an idea floated by Eric himself about how it could be done - really this is all that VB does. (very good catch on finding that document, by the way Ron)
And now, in C# 9.0, we have target-typed new expressions to implement what #EricLippert suggested.
Dictionary<string, System.Text.StringBuilder> Content = new();
You can't without repeating the type, but you could use an alias (created with a "using" statement) if the type is complex.
Is there an equivalent for the C# 4 'dynamic' keyword when using type safe VB.NET, i.e. with Option Strict On?
The equivalent is Object in VB.NET but with Option Strict Off. With Option Strict On there's no equivalent. Put another way the dynamic keyword brings Option Strict Off equivalent functionality to C#.
VB.NET always had the "dynamic" feature built in, originally called late binding. This syntax was supported forever:
Dim obj = new SomeComClass()
obj.DoSomething()
Worked on code implemented in .NET and COM, the latter being the most common use. The dynamic keyword in C# gave it that same capability. It did get changed in VB.NET version 10 however, it is now using the DLR as well. Which adds support for dynamic binding to language implementations like Python and Ruby.
The syntax is exactly the same, use the Dim keyword without As. You will however have to use Option Strict Off, Option Infer On can soften that blow a bit. It does show that C# using a specific keyword to signal dynamic binding was a pretty good move. Afaik all requests to do so in VB.NET as well have as yet been considered but not planned.
If you prefer Option Strict On, then using the Partial Class keyword so you can move some of the code into another source file is probably the most effective approach.
This will demonstrate what Basic is saying about VB not having the same granularity in this as C#. I have this piece of code in C#, that uses reflection to dynamically invoke a method at runtime:
var listResult = tgtObj.GetType().GetMethod("GetSomeData").Invoke(tgtObj, null);
The reason I'm doing this is that "GetSomeData" could be any of a number of methods, each getting different data. Which method to invoke here is dependent on a string parameter passed into this object at runtime, so, value of "GetSomeData" varies at runtime.
The signature of "GetSomeData" is:
public List<SomeResultSetClass> GetSomeData()
Each one of the methods invoked returns some sort of List<T> object. Next, I'm sending the listResult object to a generic method called Export, which looks like this:
void Export<T>(List<T> exportList, string filePath, byte fileType) where T: class;
Here's where we run into a problem. Invoke returns an object of type System.Object. Of course, a List<T> is also a System.Object, but the interface exposed is the System.Object interface, not the IList interface. If I try to execute the Export method, thus:
myExportObj.Export(listResult, parms.filePath, parms.fileType);
the code fails to compile. The error is:
The type arguments for method '...Export<T>...' cannot be inferred from the usage. Try specifying the type arguments explicitly.
No thanks!! That's what I'm trying to avoid here. The problem is that the compiler can't find the IList metadata, because it's looking at the System.Object interface. Now, you can create a new List<T>, assign (List<Whatever>) listResult to it, but that defeats the purpose of dynamic invocation in the first place.
The fix is to change var to dynamic:
dynamic listResult = tgtObj.GetType().GetMethod("GetSomeData").Invoke(tgtObj, null);
Since dynamic bypasses static type checking at compile time, we don't get a compile error. Then, when the dynamic object gets passed to the Export method, the DLR (Dynamic Language Runtime) looks to see if it can implicitly cast the object to meet the requirements of the method signature. Which of course it can.
Ok, so that's how things work in C#. With VB, the line goes like this:
Dim listResult = tgtObj.GetType().GetMethod("GetSomeData").Invoke(tgtObj, Nothing)
With Option Strict On, this line upsets the compiler, as expected. With it off, it works fine. In other words, in VB, I have to turn off the type checker for the entire module that contains the line. There is no finer granularity than that.
You can turn Option Infer On and Option Strict Off and still have something very close.
There are enough ways to handle methods and properties with late binding COM objects and type safe (Option Strict On). This when using the Microsoft.VisualBasic.Interaction.CallByName and System.Type.InvokeMember methods. (Or create a separate "partial" file where Option Strict is Off).
But to handle events with late binding from VB.NET is not as straightforward as with the dynamic type in C#. You can check the "hack" for that in Dynamic Events in VB.NET.
Note that even with Option Strict on you can still use e.g. an ExpandoObject to access properties like:
Dim doc = JsonConvert.DeserializeObject(Of ExpandoObject)("{""name"":""Bob""}")
Dim lookup as IDictionary(Of String, Object) = doc
lookup("name") ' Bob
The equivalent of the c# dynamic keyword in Vb.Net with option strict on exists as a NuGet package: Dynamitey .
After install-package Dynamitey, one can write Vb.Net code as follows:
Option Strict On : Option Infer On : Option Explicit On
Imports Dynamitey
Module Module1
Public Sub Main()
Dim o = Nothing
o = "1234567890"
Console.WriteLine(Dynamic.InvokeMember(o, "Substring", 5)) ' writes 67890
End Sub
End Module
Or the slighly more readable:
Option Strict On : Option Infer On : Option Explicit On
Imports Dynamitey
Module Module1
<Extension()>
Public Function Substring(self As Object, offset As Integer) As String
Return CType(Dynamic.InvokeMember(self, "Substring", offset), String)
End Function
Public Sub Main()
Dim o = Nothing
o = "1234567890"
Console.WriteLine(Substring(o, 5)) ' writes 67890
End Sub
End Module
Tested with VS2017 and .net Framework 4.7.2 .
Yes, ExpandoObject.
Dim DObj = New System.Dynamic.ExpandoObject()
DObj.A = "abc"
DObj.B = 123
I was just writing some quick code and noticed this complier error
Using the iteration variable in a lambda expression may have unexpected results.
Instead, create a local variable within the loop and assign it the value of the iteration variable.
I know what it means and I can easily fix it, not a big deal.
But I was wondering why it is a bad idea to use a iteration variable in a lambda?
What problems can I cause later on?
Consider this code:
List<Action> actions = new List<Action>();
for (int i = 0; i < 10; i++)
{
actions.Add(() => Console.WriteLine(i));
}
foreach (Action action in actions)
{
action();
}
What would you expect this to print? The obvious answer is 0...9 - but actually it prints 10, ten times. It's because there's just one variable which is captured by all the delegates. It's this kind of behaviour which is unexpected.
EDIT: I've just seen that you're talking about VB.NET rather than C#. I believe VB.NET has even more complicated rules, due to the way variables maintain their values across iterations. This post by Jared Parsons gives some information about the kind of difficulties involved - although it's back from 2007, so the actual behaviour may have changed since then.
Assuming you mean C# here.
It's because of the way the compiler implements closures. Using an iteration variable can cause a problem with accessing a modified closure (note that I said 'can' not 'will' cause a problem because sometimes it doesn't happen depending on what else is in the method, and sometimes you actually want to access the modified closure).
More info:
http://blogs.msdn.com/abhinaba/archive/2005/10/18/482180.aspx
Even more info:
http://blogs.msdn.com/oldnewthing/archive/2006/08/02/686456.aspx
http://blogs.msdn.com/oldnewthing/archive/2006/08/03/687529.aspx
http://blogs.msdn.com/oldnewthing/archive/2006/08/04/688527.aspx
Theory of Closures in .NET
Local variables: scope vs. lifetime (plus closures) (Archived 2010)
(Emphasis mine)
What happens in this case is we use a closure. A closure is just a special structure that lives outside of the method which contains the local variables that need to be referred to by other methods. When a query refers to a local variable (or parameter), that variable is captured by the closure and all references to the variable are redirected to the closure.
When you are thinking about how closures work in .NET, I recommend keeping these bullet points in mind, this is what the designers had to work with when they were implementing this feature:
Note that "variable capture" and lambda expressions are not an IL feature, VB.NET (and C#) had to implement these features using existing tools, in this case, classes and Delegates.
Or to put it another way, local variables can't really be persisted beyond their scope. What the language does is make it seem like they can, but it's not a perfect abstraction.
Func(Of T) (i.e., Delegate) instances have no way to store parameters passed into them.
Though, Func(Of T) do store the instance of the class the method is a part of. This is the avenue the .NET framework used to "remember" parameters passed into lambda expressions.
Well let's take a look!
Sample Code:
So let's say you wrote some code like this:
' Prints 4,4,4,4
Sub VBDotNetSample()
Dim funcList As New List(Of Func(Of Integer))
For indexParameter As Integer = 0 To 3
'The compiler says:
' Warning BC42324 Using the iteration variable in a lambda expression may have unexpected results.
' Instead, create a local variable within the loop and assign it the value of the iteration variable
funcList.Add(Function()indexParameter)
Next
For Each lambdaFunc As Func(Of Integer) In funcList
Console.Write($"{lambdaFunc()}")
Next
End Sub
You may be expecting the code to print 0,1,2,3, but it actually prints 4,4,4,4, this is because indexParameter has been "captured" in the scope of Sub VBDotNetSample()'s scope, and not in the For loop scope.
Decompiled Sample Code
Personally, I really wanted to see what kind of code the compiler generated for this, so I went ahead and used JetBrains DotPeek. I took the compiler generated code and hand translated it back to VB.NET.
Comments and variable names mine. The code was simplified slightly in ways that don't affect the behavior of the code.
Module Decompiledcode
' Prints 4,4,4,4
Sub CompilerGenerated()
Dim funcList As New List(Of Func(Of Integer))
'***********************************************************************************************
' There's only one instance of the closureHelperClass for the entire Sub
' That means that all the iterations of the for loop below are referencing
' the same class instance; that means that it can't remember the value of Local_indexParameter
' at each iteration, and it only remembers the last one (4).
'***********************************************************************************************
Dim closureHelperClass As New ClosureHelperClass_CompilerGenerated
For closureHelperClass.Local_indexParameter = 0 To 3
' NOTE that it refers to the Lambda *instance* method of the ClosureHelperClass_CompilerGenerated class,
' Remember that delegates implicitly carry the instance of the class in their Target
' property, it's not just referring to the Lambda method, it's referring to the Lambda
' method on the closureHelperClass instance of the class!
Dim closureHelperClassMethodFunc As Func(Of Integer) = AddressOf closureHelperClass.Lambda
funcList.Add(closureHelperClassMethodFunc)
Next
'closureHelperClass.Local_indexParameter is 4 now.
'Run each stored lambda expression (on the Delegate's Target, closureHelperClass)
For Each lambdaFunc As Func(Of Integer) in funcList
'The return value will always be 4, because it's just returning closureHelperClass.Local_indexParameter.
Dim retVal_AlwaysFour As Integer = lambdaFunc()
Console.Write($"{retVal_AlwaysFour}")
Next
End Sub
Friend NotInheritable Class ClosureHelperClass_CompilerGenerated
' Yes the compiler really does generate a class with public fields.
Public Local_indexParameter As Integer
'The body of your lambda expression goes here, note that this method
'takes no parameters and uses a field of this class (the stored parameter value) instead.
Friend Function Lambda() As Integer
Return Me.Local_indexParameter
End Function
End Class
End Module
Note how there is only one instance of closureHelperClass for the entire body of Sub CompilerGenerated, so there is no way that the function could print the intermediate For loop index values of 0,1,2,3 (there's no place to store these values). The code only prints 4, the final index value (after the For loop) four times.
Footnotes:
There's an implied "As of .NET 4.6.1" in this post, but in my opinion it's very unlikely that these limitations would change dramatically; if you find a setup where you can't reproduce these results please leave me a comment.
"But jrh why did you post a late answer?"
The pages linked in this post are either missing or in shambles.
There was no vb.net answer on this vb.net tagged question, as of the time of writing there is a C# (wrong language) answer and a mostly link only answer (with 3 dead links).
In VB.NET you can use an object initializer and reference the same members on the right hand side, like this:
GetValue.Add(New ArrayIndexInfo() With {
.Type = CType(NvpInfo(Index), Type),
.Name = NvpInfo(Index + 2).ToString,
.NvpValues = CType(.Type.GetField(NvpInfo(Index + 1).ToString).GetValue(Instances.First(Function(o) o IsNot Nothing AndAlso o.GetType = CType(NvpInfo(Index), Type))), NameValuePair())
})
Notice how in the line that sets .NvpValues you can reference .Type and that it's no problem.
But if you try to do that in c# (or like I did, try to convert a project from vb.net to c#), you get an error.
<variable> is not declared
I worked around it like this, which is not DRY because ((Type)NvpInfo[Index]) is repeated:
functionReturnValue.Add(new ArrayIndexInfo {
Type = (Type)NvpInfo[Index],
Name = NvpInfo[Index + 2].ToString(),
NvpValues = (NameValuePair[])((Type)NvpInfo[Index]).GetField(NvpInfo[Index + 1].ToString()).GetValue(Instances.First(o => o != null && o.GetType() == (Type)NvpInfo[Index]))
});
Why doesn't c# allow this? I think it should. I think converting legacy code to c# should be as painless as possible.
Is there a better way that I get around this and still use the object initializer?
To answer the question, I'll base myself on a simplified version of your example
VB.NET:
Dim c = New SomeClass With {
.Prop1 = "abc",
.Prop2 = .Prop1
}
Console.WriteLine(c.Prop1) 'prints "abc"
Console.WriteLine(c.Prop2) 'prints "abc"
C#
var c = new SomeClass
{
Prop1 = "abc",
Prop2 = Prop1 // won't compile. Can't reference Prop1 here.
};
Console.WriteLine(c.Prop1);
Console.WriteLine(c.Prop2);
Addressing your last question:
Is there a better way that I get around this and still use the object initializer?
So one of your concerns is that because C# doesn't allow referencing other properties in an object initialization statement, that it causes you to violate the DRY principle. But really, all you need to do is use variables:
Working C# example that doesn't violate DRY principle:
string temp = "abc";
var c = new SomeClass
{
Prop1 = temp,
Prop2 = temp
};
Console.WriteLine(c.Prop1); // prints "abc"
Console.WriteLine(c.Prop2); // prints "abc"
Why doesn't c# allow this? I think it should.
Obviously, only the designers of the language can truly answer that one. But I can at least share why I like C#'s design decision better. For instance, in the VB.NET version, if I mistakenly initialize the properties in a different order:
Dim c = New SomeClass With {
.Prop2 = .Prop1,
.Prop1 = "abc"
}
Console.WriteLine(c.Prop1) 'prints "abc"
Console.WriteLine(c.Prop2) 'prints nothing
... the code is still "legal" but I've now inadvertently introduced a bug, and Prop2 is now initialized incorrectly.
Because C# disallows this, it prevents most bugs related to the order of property initialization. I agree that in this very simplified example it's hard to imagine anyone falling for that mistake. But with more properties and more complicated initialization statements, it may not be so obvious, and mistakes can more easily be made. C# helps you avoid these subtle bugs.
I think converting legacy code to c# should be as painless as possible.
I guess you're implying that VB.NET is a legacy language? Some may take offense :)
But more seriously, are you implying that language design decisions should be made to facilitate migration from other languages? C# and VB.NET are two full featured languages in their own right. They are not meant to have matching and symmetrical language designs. If they did, what would be the point of having 2 separate languages?
No. They are 2 different languages with 2 different philosophies. We should consider ourselves lucky that the migration path is as easy as it is currently. There is no reason why it needs to be.
In C#, when calling some instance methods, We always declare a variable of that type, then assign a value to it, and call that method at last:
string str = "this is a string";
int test = str.IndexOf("a");
In Javascript, we can do this:
var test = 'sdfsldkfjskdf'.indexOf('a');
Is this kind of method calls legal in C#, say, directly use the string literal as a shorthand, without the declaration of a variable?
Yes, it's absolutely valid and fine.
I suspect you don't always declare a variable even without using literals. For example, consider:
string x = "hello";
string y = x.Substring(2, 3).Trim();
Here we're using the result of Substring as the instance on which to call Trim. No separate variable is used.
But this could equally have been written:
string y = "hello".Substring(2, 3).Trim();
The same is true for primitive literals too:
string x = 12.ToString("0000");
Ultimately, a literal is just another kind of expression, which can be used as the target of calls to instance methods.
Yes it is possible. However I would not recommend it in general, because it is confusing, and not clear to other developers what you are doing.
Yes it's valid. No problem with it.
Yes, and the best thing about it is that you don't even have to check for null.