How to specify a lamdba function in VB.NET? - vb.net

I am trying to understand the usage of lambda functions in VB.NET, using the following example. But something is not correct. Specifying 'Dim t as string = ...' doesn't work either. Can this be done?
Dim tagsList As New Dictionary(Of String, String)
Dim Name as string = "abc"
Dim t = Function(aName As String) If(tagsList.ContainsKey(aName), tagsList(aName), Nothing)
Dim Tag as string = t(Name)
Error BC30209 Option Strict On requires all variable declarations to have an 'As' clause.
Error BC30574 Option Strict On disallows late binding.

Based on the errors, it appears that you have Option Infer Off.
I'd recommend to turn Option Infer On, then your code will work exactly as presented. I can't really think of a good reason to ever have inference off.
Without type inference, you must declare the type of t. As noted in the comments, you can declare t to be [Delegate], but then you'll also have a problem at the point where you try to call. In order to get it to compile, I needed to change the right hand side to Cstr(t.DynamicInvoke(name)). A better alternative, if you're going this route, is to declare the delegate type explicitly (which must be done at class/module level), e.g.
Delegate Function RetrieveTag(ByVal name As String) As String
Then you declare t as having this type, e.g.
Dim t As RetrieveTag = ...

Related

VB.Net - Can you access the expected data type within a function?

I was wondering if there is any way to access the expected data type within a function similar to an event arg. I am doubtful that this is possible, though it would be an excellent feature.
I frequently work with (old and disorganized)Mysql databases creating interfaces through VB.Net. Often I will have an optional field which contains a NULL value in the database. I am frequently dealing with errors due to NULL and dbnull values in passing data to and from the database.
To complicate things, I often am dealing with unexpected datatypes. I might have an integer zero, a double zero, an empty string, or a string zero.
So I spend a fair amount of code checking that each entry is of the expected type and or converting NULLs to zeros or empty strings depending on the case. I have written a function ncc(null catch convert) to speed up this process.
Public Function ncc(obj As Object, tp As Type) As Object 'Null Catch Convert Function...
My function works great, but I have to manually set the type every time I call the function. It would be so much easier if it were possible to access the expected type of the expression. Here is an example of what I mean.
Dim table as datatable
adapter.fill(table)
dim strinfo as string
dim intinfo as long
strinfo = ncc(table.Rows(0).Item(0),gettype(String)) 'here a string is expected
intinfo = ncc(table.Rows(0).Item(0),gettype(Long)) 'here a long is expected
It would be so much more efficient if it were possible to access the expected type directly from the function.
Something like this would be great:
Public Function ncc(obj As Object, optional tp As Type = nothing) As Object
If tp Is Nothing Then tp = gettype(ncc.expectedtype)
That way I do not have to hard code the type on each line.
strinfo = ncc(table.Rows(0).Item(0))
You can make the ncc function generic to simplify calling it:
Public Function ncc(Of T)(obj As T) As T
If DbNull.Value.Equals(obj) Then Return Nothing
Return Obj
End Function
This kind of function will be able to in some cases infer the type, but if there's any possibility of null you'll still want to include a type name (because DBNull will be the inferred type for those values). The advantage is not needing to call gettype() and so gaining a small degree of type safety:
strinfo = ncc(Of String)(table.Rows(0).Item(0))
But I think this has a small chance to blow up at run time if your argument is not implicitly convertible to the desired type. What you should be doing is adding functions to accept a full row and return a composed type. These functions can exist as static/shared members of the target type:
Shared Function FromDataRow(IDataRow row) As MyObject
And you call it for each row like this:
Dim record As MyObject = MyObject.FromDataRow(table.Rows(i))
But, you problem still exists.
What happens if the column in the database row is null?
then you DO NOT get a data type!
Worse yet? Assume the data column is null, do you want to return null into that variable anyway?
Why not specify a value FOR WHEN its null.
You can use "gettype" on the passed value, but if the data base column is null, then you can't determine the type, and you right back to having to type out the type you want as the 2nd parameter.
You could however, adopt a nz() function (like in VBA/Access).
So, this might be better:
Public Function ncc(obj As Object, Optional nullv As Object = Nothing) As Object
If obj Is Nothing OrElse IsDBNull(obj) Then
Return nullv
End If
Return obj
End Function
So, I don't care if the database column is null, or a number, for such numbers, I want 0.
So
dim MyInt as integer
Dim MyDouble As Double
MyInt = ncc(rstData.Rows(0).Item("ContactID"), 0)
MyDouble = ncc(rstData.Rows(0).Item("ContactID"), 0)
dim strAddress as string = ""
strAddress = ncc(rstData.Rows(0).Item("Address"), "")
Since in NEAR ALL cases, you need to deal with the null from the DB, then above not only works for all data types, but also gets you on the fly conversion.
I mean, you CAN declare variables such as integer to allow null values.
eg:
dim myIntValue as integer?
But, I not sure above would create more problems than it solves.
So,
You can't get exactly what you want, because a function never has knowledge of how it's going to be used. It's not guaranteed that it will be on the right-hand side of an assignment statement.
If you want to have knowledge of both sides, you either need to be assigning to a custom type (so that you can overload the assignment operator) or you need to use a Sub instead of an assignment.
You could do something like this (untested):
Public Sub Assign(Of T)(ByVal field As Object, ByRef destination As T,
Optional ByVal nullDefault As T = Nothing)
If TypeOf field Is DBNull Then
destination = nullDefault
Else
destination = CType(field, T)
End If
End Sub
I haven't tested this, so I'm not completely certain that the compiler would allow the conversion, but I think it would because field is type Object. Note that this would yield a runtime error if field is not convertible to T.
You could even consider putting on a constraint requiring T to be a value type, though I don't think that would be likely to work because you probably need to handling String which is a reference type (even though it basically acts like a value type).
Because the destination is an argument, you wouldn't ever need to specify the generic type argument, it would be inferred.

Option Strict On and SQL stored procedure output parameter of nullable integer

There are several topics in this forum that come tantalisingly close to providing an answer to my question, but not quite what I need.
I am writing in VB.Net, retrieving data via TableAdapters and stored procedures. The stored procedures can return one or more Output parameters in the form of nullable integers.
This is some old code that I am revisiting and tidying up, including the addition of Option Strict On, and the Stored Procedure returns a zero where previously it returned a correct value. I can circumvent the problem but I would like to understand what "best practice" dictates for this circumstance.
This is the code before Option Strict was applied, and returns the correct value in the two Output parameters: RetVal (the return code, defined as an Enum) and UnspecifiedCategoryID (defined as an integer type).
Using oCategoriesTableAdapter As New YachtManagementDataSetTableAdapters.tvf_CategoriesTableAdapter
oCategoriesTableAdapter.sp_UpdateUnspecifiedCategory(
RetVal:=SQLReturn,
UnspecifiedCategoryID:=oCP.GviUnspecifiedCategorySubCategory,
UnspecifiedCategoryAttributeValueID:=oCP.GvtSystemAttributeNames(gcsSystemAttributeNameCategory).UnspecifiedEntityID,
UnspecifiedSubCategoryAttributeValueID:=oCP.GvtSystemAttributeNames(gcsSystemAttributeNameSubCategory).UnspecifiedEntityID,
VesselID:=oCP.GvoActiveVessel.ID
)
End Using
With Option Strict On, if I simply cast both to an integer, using CInt or use CType in order to remove the compiler error ("Option Strict On disallows narrowing from type 'Integer?' to type 'Integer'"), then I will always have a zero returned:
Using oCategoriesTableAdapter As New YachtManagementDataSetTableAdapters.tvf_CategoriesTableAdapter
oCategoriesTableAdapter.sp_UpdateUnspecifiedCategory(
RetVal:=CType(SQLReturn, Integer),
UnspecifiedCategoryID:=CType(oCP.GviUnspecifiedCategorySubCategory, Integer),
UnspecifiedCategoryAttributeValueID:=oCP.GvtSystemAttributeNames(gcsSystemAttributeNameCategory).UnspecifiedEntityID,
UnspecifiedSubCategoryAttributeValueID:=oCP.GvtSystemAttributeNames(gcsSystemAttributeNameSubCategory).UnspecifiedEntityID,
VesselID:=oCP.GvoActiveVessel.ID
)
End Using
I can circumvent the problem using this code:
Using oCategoriesTableAdapter As New YachtManagementDataSetTableAdapters.tvf_CategoriesTableAdapter
oCategoriesTableAdapter.sp_UpdateUnspecifiedCategory(
RetVal:=CType(SQLReturn, Integer),
UnspecifiedCategoryID:=TestNullableInteger,
UnspecifiedCategoryAttributeValueID:=oCP.GvtSystemAttributeNames(gcsSystemAttributeNameCategory).UnspecifiedEntityID,
UnspecifiedSubCategoryAttributeValueID:=oCP.GvtSystemAttributeNames(gcsSystemAttributeNameSubCategory).UnspecifiedEntityID,
VesselID:=oCP.GvoActiveVessel.ID
)
End Using
If TestNullableInteger.HasValue Then
oCP.GviUnspecifiedCategorySubCategory = TestNullableInteger.Value
Else
oCP.GviUnspecifiedCategorySubCategory = 0
End If
Another alternative is to change the data type of GviUnspecifiedCategorySubCategory itself to that of a nullable integer and check there as to whether a value has been returned.
Friend Property GviUnspecifiedCategorySubCategory As Integer?
Get
Return _lviUnspecifiedCategorySubCategory
End Get
Set(Value As Integer?)
If Value.HasValue Then
_lviUnspecifiedCategorySubCategory = Value
Else
_lviUnspecifiedCategorySubCategory = 0
End If
End Set
End Property
However, if the stored procedure has three or four Output parameters then, using this approach, the recoding starts to become onerous. The GetValueOrDefault method shows promise but, using a TableAdapter, I cannot see how this would work.
There's always the chance that I've stopped seeing the wood for the trees.
Any suggestions would be much appreciated.
From the comments to the question:
Is the FULL error message "Error BC32029 Option Strict On disallows Narrowing from type 'Integer?' to type 'Integer' in copying the value of 'ByRef' parameter 'RetVal' back to the matching argument."? – TnTinMn
Yes #TnTinMn, that is the correct full message. – Neil Miller
You are too focused on purpose of the code (database interaction) while ignoring the code syntax. The following code produces the same error message.
Sub DemoIssue()
Dim SQLReturn As Integer
SomeMethod(SQLReturn)
End Sub
Sub SomeMethod(ByRef RetVal As Integer?)
RetVal = 1
End Sub
Note that SQLReturn is a Integer type being passed as an argument to a method that takes a reference to a nullable integer.
If you would have clicked on the BC32029 in the error window to search for help on the error, you likely would have found Option Strict On disallows narrowing from type 'typename1' to type 'typename2' in copying the value of ByRef parameter 'parametername' back to the matching argument that explains:
A procedure call supplies a ByRef argument with a data type that widens to the argument's declared type, and Option Strict is On. The widening conversion is allowed when the argument is passed to the procedure, but when the procedure modifies the contents of the variable argument in the calling code, the reverse conversion is narrowing. Narrowing conversions are not allowed with Option Strict On.
To correct this error
Supply each ByRef argument in the procedure call with the same data type as the declared type, or turn Option Strict Off.
So all you need to do is define SQLReturn as Integer?
Regarding:
Ah! #TnTinMn - are you about to suggest changing the AllowDbNull property to False for the Output parameters in the TableAdapter itself? Yes, I've tested that and it works - that's a much better approach, I think. – Neil Miller
That is another option, but you need to understand the reason why it works is that TableAdapter code for sp_UpdateUnspecifiedCategory is rewritten to expect an Integer type for SQLReturn so there is no issue with ``SQLReturndefined as anInteger`
Edit To address comment:
SQLReturn is defined as an Enum with datatype Integer. I cannot define SQLReturnEnum as type Integer? How would you handle this case if you wanted to take advantage of Intellisense when using the Enum? If I coerce SQLReturn using CInt or CType I will only get a zero returned, even if I were to set the Output parameter property to AllowDbNull to False.
I believe that you are still trying to perform the type conversion on the argument sent to the method.
oCategoriesTableAdapter.sp_UpdateUnspecifiedCategory(
RetVal:=CType(SQLReturn, Integer),...
The problem with this is that CType(SQLReturn, Integer) is a function that returns a new value. As the argument is passed by reference (ByRef) it is this new value that can be modified in the method; such modification is not propagated back to SQLReturn.
Assuming SQLReturnEnum is defined like:
Public Enum SQLReturnEnum As Integer
[Default]
A
B
End Enum
Then an example of passing by reference a nullable Integer and retrieving its value as a SQLReturnEnum would be:
Sub CopyBackExample()
Dim byRefSqlReturn As Integer? ' declare a temp variable to be passed ByRef
SomeMethod(byRefSqlReturn)
' cast the temp variable's value to type SQLReturnEnum
Dim SQLReturn As SQLReturnEnum = If(byRefSqlReturn.HasValue, CType(byRefSqlReturn, SQLReturnEnum), SQLReturnEnum.Default)
End Sub

Is it better to declare the DataType of a function?

In VB .NET, What is the difference between declaring the functions's data type and ignoring it, i mean is it declared as an Object like the Variables or like something else? to be clearer which function of these two is better:
Private Function foo(ByVal text As String)
Return text
End Function
Private Function foo2(ByVal text As String) As String
Return text
End Function
Does the first one declared "As Object"? and if so, that means the second one in better, right?
The second is clearly better, the first exists only for backwards compatibility reasons. It is only allowed with Option Strict set to Off which is not recommended anyway.
This is the compiler error you normally get:
Option Strict On requires all Function, Property, and Operator
declarations to have an 'As' clause
The return type is Object for the first.

Option Strict On issues where generic type isn't known until runtime

I have the following code that has worked fine for months, but I forgot to create this class with Option Strict On so now I am going back to clean up my code correctly, however I haven't been able to figure out a way around the following issue.
I have a local variable declared like this:
Private _manageComplexProperties
Now with option strict, this isn't allowed due to no As clause which I understand, however the reason that it is like this is because the instance of the class that will be assigned to it takes a type parameter which isn't known until run time. This is solved by the following code:
Private _type As Type
*SNIP OTHER IRRELEVANT VARIABLES*
Public Sub Show()
Dim requiredType As Type = _
GetType(ManageComplexProperties(Of )).MakeGenericType(_type)
_manageComplexProperties = Activator.CreateInstance(requiredType, _
New Object() {_value, _valueIsList, _parentObject, _unitOfWork})
_result = _manageComplexProperties.ShowDialog(_parentForm)
If _result = DialogResult.OK Then
_resultValue = _manageComplexProperties.GetResult()
End If
End Sub
Again option strict throws a few errors due to late binding, but they should be cleared up with a cast once I can successfully declare the _manageComplexProperties variable correctly, but I can't seem to get a solution that works due to the type parameter not known until run time. Any help would be appreciated.
Declare your variable as Object
Private _manageComplexProperties as Object
And then you will have to persist with reflection, e.g. to call ShowDialog method:
Dim method As System.Reflection.MethodInfo = _type.GetMethod("ShowDialog")
_result = method.Invoke(_manageComplexProperties, New Object() {_parentForm})
You have to use option infer on on the top of your vb file. It enables local type inference.
Using this option allows you to use Dim without the "As" clausule, it is like
var in C#.
IntelliSense when Option Infer and Option Strict are off
IntelliSense when Option Infer is on (as you can see it has type inference)
If you do not want to use option infer on, you will have to declare the variable matching the type of the one returned by Activator.CreateInstance

Byref New Object. Is it okay top pass New Object as "byref"

Below I tried to do an Example:
Public Function UserData(ByVal UserDN As String) As DataTable
Dim myTable As DataTable = UserData_Table()
Dim dr As DataRow
dr = myTable.NewRow()
SplitOU2(UserDN, dr("OUDN"), dr("Organisation"), New Object)
dr("UserDN") = UserDN
myTable.Rows.Add(dr)
Return myTable
End Function
Below is the called method:
Friend Sub SplitOU2(ByVal inDN As String, ByRef OUDN As Object, ByRef Organisation As Object, ByRef VerksamhetTyp As Object)
By doing this I can skip to declare the in this example "useless" variable
Dim VerksamhetTyp as Object = "".
Perhaps it looks a little ugly but to have to declare unused variables can also be confusing.
Summary: Check whether or not the method really needs those parameters to be ByRef. Also check that you really don't care about anything it does to the parameters. After scrupulous checking, it's okay to do this - nothing "bad" will happen in terms of the CLR, because it's just a compiler trick under the hood.
Well, VB (unlike C#) will let you do this. Behind the scenes it's effectively creating a new variable and passing it by reference - after all, it has to for the method to be called properly. However, I'd say this is usually a bad idea. The point of ByRef is that you use the value after it's been set within the method.
Do you really need all those parameters to be ByRef in the first place? If you find yourself doing this a lot for a particular method, you could always write a wrapper method which called the original one, but didn't have the ByRef parameters itself.
(I usually find that methods with a lot of ByRef parameters indicate either a lack of understanding of reference types in .NET, or that the parameters should be encapsulated in their own type.)
Having said all of this, it's not always incorrect to ignore the value of a ByRef argument after calling the method. For example, if you just want to know whether or not some text can be parsed as an integer, then using Int32.TryParse is reasonable - but only the return value is useful to you.
The reason that I consider to use this has to do with that the method has even more parameters and that different operation overloads gets the same signature ….
The fact that it works is quite fun and somthing I became awarae óff by chance ...