This is a cousin of the question "Can CodeDom create optional arguments when generating a c# method?"
And I tried the answer given there.
Still, when I attempt to compile, I get the following error:
error BC30455: Argument not specified for parameter 'optionalParam' of 'Public Function Bar(optionalParam As Integer) As Integer
I've distilled this down to the Visual Basic Compiler not supporting either OptionalAttribute, DefaultParameterValueAttribute, or both.
Here's the distilled code I'm compiling:
Imports System.Runtime.InteropServices
Namespace SSI.RuntimeGenerated.FunctionsNamespace
Public Class Functions
Public Function Foo() As Integer
return Bar()
End Function
Public Function Bar( _
<[Optional], DefaultParameterValue(1)> _
ByVal optionalParam As Integer) _
As Integer
return optionalParam
End Function
End Class
End Namespace
Compiling this with the following command:
"C:\Windows\Microsoft.NET\Framework\v4.0.30319\vbc.exe" /t:library /out:foobar.dll foobar.vb /langversion:11
Produces the following output:
Microsoft (R) Visual Basic Compiler version 11.0.50709.17929
Copyright (c) Microsoft Corporation All rights reserved.
C:\<snip>\foobar.vb : error BC30455: Argument not specified for parameter
'optionalParam' of 'Public Function Bar(optionalParam As Integer) As Integer'.
return Bar()
~~~~~
If I change the method signature manually to be
Public Function Bar(Optional ByVal optionalParam As Integer) As Integer
then it compiles just fine.
So my questions are:
How to create optional arguments in Visual Basic generated through CodeDom?
Does vbc even support use of OptionalAttribute and/or DefaultParameterValueAttribute?
If vbc doesn't support those attributes, is there any possible way to create Optional arguments without resorting to using a CodeSnippetStatement?
If there's no other way, then is it possible to salvage other work I've done with CodeDom so I don't end up having to pretty much generate my methods by hand? Do I have to pretty much build the rest by hand inside a CodeSnippetTypeMember? Would even that work?
The OptionalAttribute is not supported in VB.NET. I cannot find any official documentation that specifically says so, but if you try to use it in a VB.NET project, it will have no effect. To create an optional parameter in VB.NET, you must use the Optional keyword, for instace:
Public Class Functions
Public Function Foo() As Integer
Return Bar()
End Function
Public Function Bar(Optional ByVal optionalParam As Integer = 1) As Integer
Return optionalParam
End Function
End Class
Related
There is a legacy Visual Basic class library targetting .Net Standard 2.0 currently:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualBasic" Version="10.3.0" />
</ItemGroup>
</Project>
There is also a class inside
Public Class Class1
Public Shared Function TryGetInteger(ByVal value As Object) As Integer
Dim out As Integer
If Integer.TryParse(value, out) Then
Return out
End If
Return Integer.MinValue
End Function
Public Shared Function BooleanToInt(ByVal bValue As Object) As Integer
If (bValue = DBNull.Value) Then
Return 0
ElseIf bValue = True Then
Return 1
Else
Return 2
End If
End Function
End Class
At this point everything works fine.
But changing <TargetFramework>netstandard2.0</TargetFramework> to 2.1 breaks the build with the following error in the Class1.TryGetInteger function:
Requested operation is not available because the runtime library function 'Microsoft.VisualBasic.CompilerServices.Conversions.ChangeType' is not defined
How can this be fixed?
EDIT: Please disregard below idea:
The first idea comes from this closed github issue, that suggests adding <VBRuntime>Default</VBRuntime> to the project file, but this results in error:
could not find library 'Microsoft.VisualBasic.dll'
But it is already linked as a package... Any ideas?
EDIT: As mentioned is the comments, my take on the linked github issue is a bit misleading, so I guess I'm back to the beginning with the Requested operation is not available error.
Integer.TryParse() expects a String, but the input argument type is Object.
You can fix the issue like this:
Public Shared Function TryGetInteger(value As Object) As Integer
Dim out As Integer
If Integer.TryParse(value.ToString(), out) Then Return out
Return Nothing 'Same as Integer.MinValue in this context
End Function
The extra ToString() call is the same thing the old framework was doing for you implicitly. Now you're aware of it.
But really I'd do this:
Public Shared Function TryGetInteger(value As String) As Integer
Dim out As Integer
If Integer.TryParse(value, out) Then Return out
Return Nothing
End Function
Which will probably bring up a whole new set of compiler errors, but there will be value in going back and fixing them at those calling locations. Most of the errors can be fixed by adding .ToString() to the end of value when calling the function, and any that can't were almost certainly hiding bugs.
Similarly for the BooleanToInt() function, I'd use explicit types, and maybe overloading to get better results, like this:
Public Shared Function BooleanToInt(bValue As DBNull) As Integer
Return 0 'If this overload was selected, we know we want 0
End Function
Public Shared Function BooleanToInt(bValue As Boolean) As Integer
If bValue Then Return 1
Return 2
End Function
Public Shared Function BooleanToInt(bValue As Object) As Integer
Return 2
End Function
As a rule of thumb, anywhere you need to use the Object type directly is a magnet for bugs. It's a smell in the code and something to avoid.
The class methods are relying on the implicit type conversions implemented by the VB compiler when Option Strict is set to Off.
The safest way to rewrite the code would be to use a de-compiler and implement the conversion operations used by the compiler. That could get quite involved.
If there is some sanity in the code that calls the the class methods, then a simple rewrite like the following can be done to avoid the usage of compiler's helper conversion methods.
Public Shared Function TryGetInteger(ByVal value As Object) As Integer
Dim out As Integer
If Integer.TryParse(value.ToString, out) Then
Return out
End If
Return Integer.MinValue
End Function
Public Shared Function BooleanToInt(ByVal bValue As Object) As Integer
If (bValue Is DBNull.Value) Then
Return 0
Else
If TypeOf bValue Is Boolean Then
Return If(DirectCast(bValue, Boolean), 1, 2)
Else
Throw New ArgumentException("type of bValue must be either DbNull or Boolean")
End If
End If
End Function
I implemented a VB.NET DLL with a simple test function:
<ComVisible(True)>
Function TestString ( <MarshalAs(UnmanagedType.BStr)> xyz As String) As <MarshalAs(UnmanagedType.BStr)> String
Dim y As Integer
TestString = "Hello"
End Function
The function is dead simple. In VBA I declare the function appropriately:
Public Declare Function TestString Lib "myDLL.dll" (xyz As String) As String
From VBA I also obviously load the DLL. However the issue is that when I run the function like so:
Dim st as String
st = "Hello"
Debug.Print TestString(ByVal st)
I get a message saying bad DLL calling convention. On the other hand when I remove the <MarshalAs(UnmanagedType.BStr)> the function works BUT crashes shortly after printing "Hello".
What am I doing wrong?
Public Declare Function is the syntax used to call functions exported from a DLL, this is a different mechanism from COM.
To use COM add a reference to the DLL, create an instance of the class containing TestString then call across that instance: r = classInstance.TestString("Hello")
Came across something I found interesting and would love an explanation.
Edit
This question is not meant to be answered with what should be done to fix it. I know the fixes. I want an explanation of why the compiler does what it does. Ex. Are the private functions not considered given this scenario?
Problem
I have a class that has a public shared(static) function called WhatIs. WhatIs takes a parameter that has a collection of objects. the code iterates over this collection and calls a WhatIs function that has a parameter matching type of what the object is.
When executed, an InvalidCastException exception is thrown because the execution is trying to call the WhatIs function that started this, not the one for the type provided.
That's weird, but what made it odd to me was when you change the private shared functions to public shared then it works fine.
Even odder, when you explicit cast the object then it works even if the function is private.
What?! someone please explain
Code
the guts:
Public Class House
Public Property Furniture As ICollection(Of Object)
Public Sub New()
Furniture = New List(Of Object)
End Sub
End Class
Public Class Chair
Public Property IsComfortable As Boolean
End Class
Public Class Table
Public Seats As Integer
End Class
Public Class HouseExaminer
Public Shared Function WhatIs(thing As House) As String
Dim isA As String = "a house that contains "
For Each item In thing.Furniture
isA &= WhatIs(item)
Next
Return isA
End Function
Private Shared Function WhatIs(thing As Chair) As String
Return "a " & If(thing.IsComfortable, "comfortable", "uncomfortable") & " chair "
End Function
Private Shared Function WhatIs(thing As Table) As String
Return "a table that seats " & thing.Seats & " iguanas"
End Function
End Class
to test
Imports System.Text
Imports Microsoft.VisualStudio.TestTools.UnitTesting
Imports stuff
<TestClass()>
Public Class HouseExaminerTests
<TestMethod()>
Public Sub TestWhatIs()
Dim given As New House()
Dim expected As String
Dim actual As String
given.Furniture.Add(New Chair() With {.IsComfortable = True})
given.Furniture.Add(New Table() With {.Seats = 4})
expected = "a house that contains a comfortable chair a table that seats 4 iguanas"
actual = HouseExaminer.WhatIs(given)
Assert.Equals(expected, actual)
End Sub
End Class
result
debug the test and you get this:
InvalidCastException
Method invocation failed because 'Public Shared Function WhatIs(thing As stuff.House) As String' cannot be called with these arguments:
Argument matching parameter 'thing' cannot convert from 'Chair' to 'House'.
These changes make it work but why?!
make em public
change the private shared functions in HouseExaminer to public, rerun test. spoiler, it works
explicitly cast the objects
change them back to private then replace
isA &= WhatIs(item)
with
If TypeOf item Is Chair Then isA &= WhatIs(CType(item, Chair))
If TypeOf item Is Table Then isA &= WhatIs(CType(item, Table))
rerun test, and what do u know, it works
Firstly, it looks like you have implicit conversions turned on. That is the start of the issue. Secondly, you define Furniture as a List(of Object). Your first call to WhatIs is succeeding. The compiler is making a best guess as to which overload to use when passing what it sees as simply Object as it iterates through thing.Furniture, and it determines the public static version of the WhatIs method to be the most appropriate. It then attempts to implicitly convert Object to House, and inevitably fails.
Why does casting work? Because it takes the guess work out of determining which overload to use.
Moral of the story is: Don't make the compiler guess. Implicit conversion can lead to tricky bugs.
Edit: Why doesn't the compiler see the other overloaded functions?
The compiler has to determine the correct overload to use at compile time. It does not wait until runtime to determine which overload to use, and therefore doesn't have the luxury of inspecting the type of the object to determine the most appropriate overload.
Since the compiler only knows that furniture is a List(Of Object), technically (with implicit conversion turned on) all three of the overloads are deemed "appropriate," but the compiler must choose one. It ranks the possible overload candidates, and chooses the public version ahead of the private ones.
Use always
Option Strict On
You cannot make it more flexible by adding Methods equal in name, just with different parametertypes.
Update
Private Function ShowMe(data As Integer) As String
Return data.ToString
End Function
Private Function ShowMe(data As String) As String
Return data
End Function
Private Function ShowMe(data As Double) As String
Return data.ToString
End Function
Dim bla As New List(Of Object)
if you then call
bla.Add(12)
bla.Add("hi")
bla.Add(1.2)
Dim text As String
text = ShowMe(bla(0))
text = ShowMe(bla(1))
text = ShowMe(bla(2))
then the compiler will always complain that the correct method does not exist, because the correct method is not selected by checking the type, instead it is selected by the definition, for which type the container is defined for.
Private Function ShowMe(data As Object) As String
Return data.ToString
End Function
this would be called for all integer, doubles and strings. If it is not available, then some methods are used that can do some kind of automatic conversion. Thats why you can put an integer in a float, or put a number in a string.
One way would be to check for its type and do an explizit type conversion
For Each ele As Object In bla
If TypeOf ele Is Integer Then
text = ShowMe(CInt(ele))
ElseIf TypeOf ele Is Double Then
text = ShowMe(CDbl(ele))
Else
text = ShowMe(CStr(ele))
End If
Next
But this is still not so clean. If you want to access properties that all objects should support, then put them in a container and define the type as something that assures that those properties exist.
I am looking for sample linq code snippet which uses System.Linq.Dynamic against a datatable.
Dim entities = (From ent In dt.AsEnumerable().Where(String.Format("IsUSFederal == {0}", "true")) _
Select Description = ent("Description"), Acronym = ent("Acronym")).ToList
I am getting an error "there is no accessible Where can be called with these arguments". I have included the DynamicLinq.vb file and the application compiles fine (aside from this error). I have included Imports System.Linq.Dynamic but it doesn't appear to be working.
Any ideas?
thx
The Enumerable.Where takes a Func(Of TSource, Boolean) as argument and your passing a String.
(update)
Didn't catch the Dynamic library part ... sorry. I think you need to do this:
dt.AsQueryable()
Because the extension methods on the library are defined as:
<Extension()> _
Public Function Where(ByVal source As IQueryable, ByVal predicate As String,
ByVal ParamArray values() As Object) As IQueryable
I got a Utility module since VB.NET doesn't have static class like C# and Module is the static class in VB.NET. The reason that I use module is because I'm using the Extension method and it can only be use in Module.
I can't reference to this module but if I put my code in a class. I can reference to it without any problem. What could be the reason? I missed C#.
Edit: The module is inside a class library call Utility.
You need to mark the module as Public Module.
I can't reference to this module but if i put my code in a class. I can reference to it without any problem. Does anyone know why?
Because Modules in VB aren't classes and can't be used to instantiate objects. Rather, they're something similar to namespaces, with the difference that namespaces can't contain functions directly. So the reason for modules is to provide a way to group functions logically that don't belong to a class.
This makes a lot of sense when you consider that not everything logically belongs to a class. Consider System.Math. There is absolutely no reason to make that a class, other than a weird OOP purism.
By the way, you can't reference static classes in C# either, at least not if I understand correctly what you mean by “reference”. Perhaps you can clarify this.
.NET compilers can take any type of language syntax and turn it into a .NET equivalent. Sometimes there is a one for one correspondence other times there isn't.
By using the .NET Reflector you can see what the compiler is really doing.
In VB.NET the module exists because of the heritage inherited from Visual BASIC and partly from Microsoft BASIC.
The VB.NET compiler will take this
Public Module CoreModule
Dim R As New System.Random(CInt(Microsoft.VisualBasic.Timer))
Public Function D(ByVal Roll As Integer) As Integer
Return R.Next(0, Roll) + 1
End Function
Public Function _1D6() As Integer
Return D(6)
End Function
Public Function _2D6() As Integer
Return D(6) + D(6)
End Function
Public Function _3D6() As Integer
Return D(6) + D(6) + D(6)
End Function
Public Function _4D6() As Integer
Return D(6) + D(6) + D(6) + D(6)
End Function
Public Function CRLF() As String
Return Microsoft.VisualBasic.ControlChars.CrLf
End Function
End Module
And turn it into this (code left out for brevity)
Public NotInheritable Class CoreModule
' Methods
Shared Sub New()
Public Shared Function _1D6() As Integer
Public Shared Function _2D6() As Integer
Public Shared Function _3D6() As Integer
Public Shared Function _4D6() As Integer
Public Shared Function CRLF() As String
Public Shared Function D(ByVal Roll As Integer) As Integer
' Fields
Private Shared R As Random
End Class
In C# the equivalent is this
public sealed class CoreModule
{
// Fields
private static Random R;
// Methods
static CoreModule();
public static int _1D6();
public static int _2D6();
public static int _3D6();
public static int _4D6();
public static string CRLF();
public static int D(int Roll);
}
All that matter is that the emitted CIL does the job correctly.
This capability is the main reason why so many older Visual BASIC 6 programmers are highly annoyed at MS's changes to the language. For example the keyword Integer emitting a Int32 instead of a Int16.
Modules are exposed to other assemblies referencing the original assembly as long as the module is declared public.
Maybe the methods/subs aren't public? I had that problem once, and it would allow access local code in your class, but not if it was outside your class and marked "Private".
Imports System.Web
Imports System.Web.UI
Module ResponseHelper
<System.Runtime.CompilerServices.Extension()> _
Public Sub Redirect(ByVal response As Net.HttpWebResponse, _
ByVal url As String, ByVal target As String, _
ByVal windowFeatures As String)
If String.IsNullOrEmpty(target) Or _
target.Equals("_self", StringComparison.OrdinalIgnoreCase) And _
String.IsNullOrEmpty(windowFeatures) Then
response.Redirect(url, target, windowFeatures)
Else
Dim page As Page = CType(HttpContext.Current.Handler, Page)
If page Is Nothing Then
Throw New InvalidOperationException("Cannot redirect to new window outside Page context.")
End If
url = page.ResolveClientUrl(url)
Dim script As String
If String.IsNullOrEmpty(windowFeatures) Then
script = "window.open(""{0}"", ""{1}"", ""{2}"";"
Else
script = "window.open(""{0}"", ""{1}"");"
End If
script = String.Format(script, url, target, windowFeatures)
ScriptManager.RegisterStartupScript(page, GetType(Page), "Redirect", script, True)
End If
End Sub
End Module
I don't understand what you are asking.
VB.NET does have static classes, just like in C#, because in VB.NET a Module IS a static class. They are one and the same. Anything you can do with a static class you can do with a Module. Perhaps you haven't marked your Public/Private/Protected access correctly?