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
Related
I am new to C#. I have read that the use of the keyword dynamic is somewhat controversial, and that other methods may be preferred depending on the use case. I want to make sure that my use of dynamic is appropriate for the language and the case. I'm working with this code:
public void myMethod(Action<T> myFunc) {
dynamic arg = "example"; // a string
myFunc(arg);
}
I don't know what the type T will be until runtime, and so I thought that dynamic would be useful. In the event that T is a string, I want to invoke myFunc with that argument. Is using dynamic the best way to do this in C#?
edit: To give more context, if T is string, then I want to pass arg to myFunc. If I don't use dynamic, I get an error that "cannot convert from 'string' to 'T'". Using dynamic solves this problem, I'm just not sure if it's the best way to solve it.
No. That is not an appropriate usage. You code will fail at runtime if T is anything other than string. That is the thing with dynamic, it turns of compiler checks, it does not mean your code will run, only that compiler errors turn into runtime errors. Dynamic is intended for interoperability with dynamic languages, or COM, where you are forced to cast objects at runtime anyway, so dynamic just makes it easier, without loosing any actual safety.
If you want to give myFunc a string, declare it as Action<string>. If you want to create a new object and give it to myFunc, add a new() restriction, i.e.
public void myMethod(Action<T> myFunc) where T : new(){
myFunc(new T());
}
If you don't know how to construct T, let the caller give it to you:
public void myMethod(Action<T> myFunc, T value) {
myFunc(value);
}
You can also use Func<T> to give your method a delegate that constructs values on demand.
Also Action<T> would be called a "generic delegate". Even if c++ templates and c# generics are used for a similar purpose, they work in a very different way. In effect c# generics is more restrictive, but also makes compiling much faster and easier since you do not have to generate code for each specialization until it is jitted.
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.
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).
I am having more problems with my converting of vb.net to c#.net.
I have some files in vb.net that have "Option Strict Off" to allow for bad programming. When I convert to c# I use "dynamic" until I come back and fix problems, and this works in all cases
But now I have this code:
Public Class ContractResults
'Big class definition
Public Shared Sub CleanCache()
'Code here
End Sub
End Class
And in a file with Option Strict Off:
Public Sub VerifyResults(result as Object)
'Here, result is normally ContractResults
'first it check for that then call this:
result.CleanCache()
End Sub
In c# I use "dynamic", but a runtime error pops up when I call "static" method with dynamic reference. In vb.net, I can called "shared" sub from instance, but in c# this is not allowed
Exception:
"Microsoft.CSharp.RuntimeBinder.RuntimeBinderException"
"Member 'ContractTypes.ContractResults.CleanCache()' cannot be accessed with an instance reference; qualify it with a type name instead"
It seems I must convert code to use actual type, but then this means much rewriting of more parts. Is anyone able to show another way?
I want to make sure you do not think I can use
(result as ContractResults).CleanCache();
Because all types that may be passed in have "CleanCache()" method, but do not inherits from anything the same other than "Object". There are many types (30!) that have this "static" method and so that is why it uses Option Strict Off
You could use reflection:
result.GetType().InvokeMember("CleanCache", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy, null, null, new object[0]);
(untested)
As the error suggests, you have to call a static method from the class, not an instance:
ContractResults.CleanCache();
You could put your 30 types into a switch statement, but this would be ugly.
I'm sorry to say this, but the original decision to turn Option Strict Off was a bad one. Somehow I doubt that the code that was originally written does what you think it's doing anyway.
It's time to just go ahead and fix that problem.
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.