VB.NET Sub inside of a Function? What is this? - vb.net

So I'm reading through my source code looking for places to improve the code when I come across this unholy chunk of code.
Public Function ReadPDFFile(filePath As String,
Optional maxLength As Integer = 0) As List(Of String)
Dim sbContents As New Text.StringBuilder
Dim cArrayType As Type = GetType(PdfSharp.Pdf.Content.Objects.CArray)
Dim cCommentType As Type = GetType(PdfSharp.Pdf.Content.Objects.CComment)
Dim cIntegerType As Type = GetType(PdfSharp.Pdf.Content.Objects.CInteger)
Dim cNameType As Type = GetType(PdfSharp.Pdf.Content.Objects.CName)
Dim cNumberType As Type = GetType(PdfSharp.Pdf.Content.Objects.CNumber)
Dim cOperatorType As Type = GetType(PdfSharp.Pdf.Content.Objects.COperator)
Dim cRealType As Type = GetType(PdfSharp.Pdf.Content.Objects.CReal)
Dim cSequenceType As Type = GetType(PdfSharp.Pdf.Content.Objects.CSequence)
Dim cStringType As Type = GetType(PdfSharp.Pdf.Content.Objects.CString)
Dim opCodeNameType As Type = GetType(PdfSharp.Pdf.Content.Objects.OpCodeName)
Dim ReadObject As Action(Of PdfSharp.Pdf.Content.Objects.CObject) = Sub(obj As PdfSharp.Pdf.Content.Objects.CObject)
Dim objType As Type = obj.GetType
Select Case objType
Case cArrayType
Dim arrObj As PdfSharp.Pdf.Content.Objects.CArray = DirectCast(obj, PdfSharp.Pdf.Content.Objects.CArray)
For Each member As PdfSharp.Pdf.Content.Objects.CObject In arrObj
ReadObject(member)
Next
Case cOperatorType
Dim opObj As PdfSharp.Pdf.Content.Objects.COperator = DirectCast(obj, PdfSharp.Pdf.Content.Objects.COperator)
Select Case System.Enum.GetName(opCodeNameType, opObj.OpCode.OpCodeName)
Case "ET", "Tx"
sbContents.Append(vbNewLine)
Case "Tj", "TJ"
For Each operand As PdfSharp.Pdf.Content.Objects.CObject In opObj.Operands
ReadObject(operand)
Next
Case "QuoteSingle", "QuoteDbl"
sbContents.Append(vbNewLine)
For Each operand As PdfSharp.Pdf.Content.Objects.CObject In opObj.Operands
ReadObject(operand)
Next
Case Else
'Do Nothing
End Select
Case cSequenceType
Dim seqObj As PdfSharp.Pdf.Content.Objects.CSequence = DirectCast(obj, PdfSharp.Pdf.Content.Objects.CSequence)
For Each member As PdfSharp.Pdf.Content.Objects.CObject In seqObj
ReadObject(member)
Next
Case cStringType
sbContents.Append(DirectCast(obj, PdfSharp.Pdf.Content.Objects.CString).Value)
Case cCommentType, cIntegerType, cNameType, cNumberType, cRealType
'Do Nothing
Case Else
Throw New NotImplementedException(obj.GetType().AssemblyQualifiedName)
End Select
End Sub
Using pd As PdfSharp.Pdf.PdfDocument = PdfSharp.Pdf.IO.PdfReader.Open(filePath, PdfSharp.Pdf.IO.PdfDocumentOpenMode.ReadOnly)
For Each page As PdfSharp.Pdf.PdfPage In pd.Pages
ReadObject(PdfSharp.Pdf.Content.ContentReader.ReadContent(page))
If maxLength > 0 And sbContents.Length >= maxLength Then
If sbContents.Length > maxLength Then
sbContents.Remove(maxLength - 1, sbContents.Length - maxLength)
End If
Exit For
End If
sbContents.Append(vbNewLine)
Next
End Using
'Return sbContents.ToString
Dim ReturnList As New List(Of String)
For Each Line In sbContents.ToString.Split(vbNewLine)
If String.IsNullOrWhiteSpace(Line.Trim) Then
Else
ReturnList.Add(Line.Trim)
End If
Next
Return ReturnList
End Function
All this does is read the text parts of a PDF using PDFSharp. What caught my eye however was line 17. Is that a Sub inside of the function?
So, what exactly is this Sub inside of a function? I didn't write this code so I've never seen anything like this before.
How does this work exactly and why wouldn't I use a function to do the processing and then return the results?
In short, my question is, what is this, how does it work, and why would I want to use something like this?

That's a so-called Lambda expression. They're used to create inline (or more correctly: in-method) methods, which makes them more dynamic than normal methods.
In your example a lambda expression is not necessary and only makes the code harder to understand. I suppose the author of that code wrote a lambda expression instead of a separate method in order to not expose ReadObject to any outside code.
One of the best uses for a lambda expression IMO is when you want to make thread-safe calls to the UI thread, for instance:
If Me.InvokeRequired = True Then
Me.Invoke(Sub() TextBox1.Text = "Process complete!")
Else
TextBox1.Text = "Process complete!"
End If
...where the same code without a lambda would look like this:
Delegate Sub UpdateStatusTextDelegate(ByVal Text As String)
...somewhere else...
If Me.InvokeRequired = True Then
Me.Invoke(New UpdateStatusTextDelegate(AddressOf UpdateStatusText), "Process complete!")
Else
UpdateStatusText("Process complete!")
End If
...end of somewhere else...
Private Sub UpdateStatusText(ByVal Text As String)
TextBox1.Text = Text
End Sub
There are also other examples where lambda expressions are useful, for instance if you want to initialize a variable but do some processing at first:
Public Class Globals
Public Shared ReadOnly Value As Integer = _
Function()
DoSomething()
Dim i As Double = CalculateSomething(3)
Return Math.Floor(3.45 * i)
End Function.Invoke()
...
End Class
Yet another usage example is for creating partially dynamic event handlers, like this answer of mine.

Related

Setting Values Using Reflection

I am using VB.NET. I have created a small scale test project that works similar to my program. I am trying to say something like: getObjectType(object1) if Object1.getType() = "ThisType" then get the properties. Each object contains an ID and I would like to do this: Object1.Id = -1 (I know it won't be that short or easy). I thought there was a way to do this by using something like: Object1.SetValue(Value2Change, NewValue) but that doesn't work and I'm not sure how to exactly do this. Below is my code. Thank You!
Module Module1
Sub Main()
Dim Db As New Luk_StackUp_ProgramEntities
Dim Obj1 As IEnumerable(Of Stackup) = (From a In Db.Stackups).ToList
Dim Obj2 As IEnumerable(Of Object) = (From a In Db.Stackups).ToList
Dim IdNow As Integer = Obj1(0).IdStackup
Dim StackUpNow As Stackup = (From a In Db.Stackups Where a.IdStackup = IdNow).Single
Console.WriteLine(StackUpNow)
getInfo(StackUpNow)
getInfo(Obj1(0), Obj1(0))
areObjectsSame(Obj1(0), Obj1(67))
switchObjects(Obj1(0), Obj2(1))
getObjectValues(Obj2(55))
Console.WriteLine("========================================")
TestCopyObject(StackUpNow)
ChangeObjectValues(StackUpNow)
Console.ReadKey()
End Sub
Private Sub ChangeObjectValues(Object1 As Object)
Console.WriteLine("Changing Object Values")
Dim myField As PropertyInfo() = Object1.GetType().GetProperties()
'Dim Index As Integer 'Did not find value
'For Index = 0 To myField.Length - 1
' If myField(Index).ToString.Trim = "IdStackup" Then
' Console.WriteLine("Found the ID")
' End If
'Next
If Object1.GetType().Name = "Stackup" Then
'Set the Value
End If
End Sub
You can use PropertyInfo.SetValue to set the value using reflection. You could also use a LINQ SingleOrDefault query to simplify finding the correct PropertyInfo, so you could do something like this:
Private Sub ChangeObjectValues(Object1 As Object)
Console.WriteLine("Changing Object Values")
Dim t As Type = Object1.GetType()
If t.Name = "Stackup" Then
Dim myField As PropertyInfo = t.GetProperties() _
.SingleOrDefault(Function(x) x.Name = "IdStackup")
If myField IsNot Nothing Then
Console.WriteLine("Found the ID")
myField.SetValue(Object1, -1)
End If
End If
End Sub
It's not clear from the question if you really need to use reflection - perhaps a common interface to define the id property, or just type checking and casting, etc. would be better.
Well, I struggled to see how your code example applied to your question, but if you are simply asking how to set the ID of an object using reflection, this code might help you. The trick is that a property is typically handled using a set and a get method.
Imports System.Web.UI.WebControls
Imports System.Reflection
Module Module1
Sub Main()
Dim tb As New Label()
Dim t As Type = tb.GetType()
If TypeOf tb Is Label Then
Dim mi As MethodInfo = t.GetMethod("set_ID")
mi.Invoke(tb, New Object() {"-1"})
End If
Console.WriteLine(tb.ID)
Console.ReadLine()
End Sub
End Module

vb.net - How to Declare new task as SUB with parameters

As you know we have a new syntax in vb.net with possibility to create inline tasks so we could run it asynchronously.
This is the correct code:
Dim testDeclaring As New Task(Sub()
End Sub)
testDeclaring.Start()
but now I need to pass a parameter in the subroutine and I can't find correct syntax for that.
Is it possible any way?
It's not possible. However, you could just use the parameters from the current scope:
Public Function SomeFunction()
Dim somevariable as Integer = 5
Dim testDeclaring As New Task(Sub()
Dim sum as integer = somevariable + 1 ' No problems here, sum will be 6
End Sub)
testDeclaring.Start()
End Function
If you want to pass a parameter you could do this
Dim someAction As Action(Of Object) = Sub(s As Object)
Debug.WriteLine(DirectCast(s, String))
End Sub
Dim testDeclaring As New Task(someAction, "tryme")
testDeclaring.Start()
Dont know if you looking for this:
Dim t As Task = New Task(Sub() RemoveBreakPages(doc))
Sub RemoveBreakPages(ByRef doc As Document)
Dim paragraphs As NodeCollection = doc.GetChildNodes(NodeType.Paragraph, True)
Dim runs As NodeCollection = doc.GetChildNodes(NodeType.Run, True)
For Each p In paragraphs
If CType(p, Paragraph).ParagraphFormat().PageBreakBefore() Then
CType(p, Paragraph).ParagraphFormat().PageBreakBefore = False
End If
Next
End Sub
Regards.

Function call from Arraylist element not working

I am trying to get a function for a class assignment to work however as the program hits the specific line in question it dies off and nothing after this line will execute. The program does not lock up, just the current execution path dies.
I have tried running debugging but much the same happens. Once I hit the link that should call a function from the object stored in the Arraylist element the break point at the actual function that should be called is not hit and nothing further happens.
Public Structure Appliances
' Create New Appliance object
Public Sub New(name As String, pusage As Double)
aName = name
aPUsage = pusage
End Sub
' Create New Washer Object
Public Sub New(name As String, pusage As Double, wusage As Double)
aName = name
aPUsage = pusage
aWUsage = wusage
End Sub
' Functions
Public Function getAName()
Return aName
End Function
Public Function getAPUsage()
Return aPUsage
End Function
Public Function getAWUsage()
Return aWUsage
End Function
Dim aName As String ' Appliance Name
Dim aPUsage As Double ' Appliane Power Usage
Dim aWUsage As Double ' Appliance Water Usage
End Structure
...
Public Class Form1
...
Dim appList As New ArrayList() ' Create an arraylist appliance objects
Public appTemp As Appliances ' To store appliance objects until they can be added to the arraylist
...
Private Function getAppInfo()
getAppInfo = Nothing
Do While fInStream.Peek() <> -1
s = fInStream.ReadLine() ' Get a line from the file and set s to it
Dim words As String() = s.Split(New Char() {","c}) ' Split the line contents along commas and set those parts into words
words(0) = words(0).Replace("_", " ") ' Reaplce underscores with spaces
If (words.Count = 3) Then ' If words contains the washer appliance
appTemp = New Appliances(words(0), Double.Parse(words(1)), Double.Parse(words(2)))
appList.Add(appTemp)
Else ' For all other appliances
appTemp = New Appliances(words(0), Double.Parse(words(1)))
appList.Add(appTemp)
End If
Loop
End Function
Private Function setUsage(name As String)
setUsage = Nothing
' Find appliance
For i = 0 To appList.Count
If (name = appList(i).getAName()) Then
If (name = "Washer") Then
s = appList(i).getWUsage() ' !!!This is the line where the execution dies at, nothing after this line is processed and the function call is not completed
txtbGPH.Text = s
End If
MsgBox("Test 1")
Exit For
ElseIf (i = appList.Count) Then
MsgBox("Appliance could not be found")
End If
Next
End Function
End Class
Use a List(Of X) instead of ArrayList if you are going to insert only one type:
Dim appList As New List(Of Appliances)
And I recommend you to declare your temp var inside the methods unless is necessary. Anyway, in this case you don't need it, you can add your var in this way:
appList.Add(New Appliances(words(0), Double.Parse(words(1))))
With this use (using lists) you won't need to use arraylistObj.Item(i).Method() and you can simply use the common way:
s = appList(i).getWUsage()
Nevermind, I figured it out just now. I did not know that arraylists are not "arraylists" but a collection. I thought maybe it would act like other collection oriented objects and that you have to use a .item(i) to access the elements, which turns out to be the case.
txtbGPH.text = appList.item(i).getAWusage()
produces the proper behavior and the rest of the code after the problematic line indicated in the OP executes as does the break point set at the called function.

VB.Net List.Find. Pass values to predicate

Having a bit of trouble using the List.Find with a custom predicate
i have a function that does this
private function test ()
Dim test As Integer = keys.Find(AddressOf FindByOldKeyAndName).NewKey
here's the function for the predicate
Private Shared Function FindByOldKeyAndName(ByVal k As KeyObj) As Boolean
If k.OldKey = currentKey.OldKey And k.KeyName = currentKey.KeyName Then
Return True
Else
Return False
End If
End Function
by doing it this way means i have to have a shared "currentKey" object in the class, and i know there has to be a way to pass in the values i'm interested in of CurrentKey (namely, keyname, and oldkey)
ideally i'd like to call it by something like
keys.Find(AddressOf FindByOldKeyAndName(Name,OldVal))
however when i do this i get compiler errors.
How do i call this method and pass in the values?
You can cleanly solve this with a lambda expression, available in VS2008 and up. A silly example:
Sub Main()
Dim lst As New List(Of Integer)
lst.Add(1)
lst.Add(2)
Dim toFind = 2
Dim found = lst.Find(Function(value As Integer) value = toFind)
Console.WriteLine(found)
Console.ReadLine()
End Sub
For earlier versions you'll have to make "currentKey" a private field of your class. Check my code in this thread for a cleaner solution.
I have an object that manages a list of Unique Property Types.
Example:
obj.AddProperty(new PropertyClass(PropertyTypeEnum.Location,value))
obj.AddProperty(new PropertyClass(PropertyTypeEnum.CallingCard,value))
obj.AddProperty(new PropertyClass(PropertyTypeEnum.CallingCard,value))
//throws exception because property of type CallingCard already exists
Here is some code to check if properties already exist
Public Sub AddProperty(ByVal prop As PropertyClass)
If Properties.Count < 50 Then
'Lets verify this property does not exist
Dim existingProperty As PropertyClass = _
Properties.Find(Function(value As PropertyClass)
Return value.PropertyType = prop.PropertyType
End Function)
'if it does not exist, add it otherwise throw exception
If existingProperty Is Nothing Then
Properties.Add(prop)
Else
Throw New DuplicatePropertyException("Duplicate Property: " + _
prop.PropertyType.ToString())
End If
End If
End Sub
I haven't needed to try this in newer versions of VB.Net which might have a nicer way, but in older versions the only way that I know of would be to have a shared member in your class to set with the value before the call.
There's various samples on the net of people creating small utility classes to wrap this up to make it a little nicer.
I've found a blog with a better "real world" context example, with good variable names.
The key bit of code to Find the object in the list is this:
' Instantiate a List(Of Invoice).
Dim invoiceList As New List(Of Invoice)
' Add some invoices to List(Of Invoice).
invoiceList.Add(New Invoice(1, DateTime.Now, 22))
invoiceList.Add(New Invoice(2, DateTime.Now.AddDays(10), 24))
invoiceList.Add(New Invoice(3, DateTime.Now.AddDays(30), 22))
invoiceList.Add(New Invoice(4, DateTime.Now.AddDays(60), 36))
' Use a Predicate(Of T) to find an invoice by its invoice number.
Dim invoiceNumber As Integer = 1
Dim foundInvoice = invoiceList.Find(Function(invoice) invoice.InvoiceNumber = invoiceNumber)
For more examples, including a date search, refer to Mike McIntyre's Blog Post

VB.NET Generic Function

What I want to do is, based on the type of T do different opperations. Below is a simple example of my problem.
Public Shared Function Example(Of T)() As T
Dim retval As T
If TypeOf retval Is String Then
Dim myString As String = "Hello"
retval = myString
ElseIf TypeOf retval Is Integer Then
Dim myInt As Integer = 101
retval = myInt
End If
Return retval
End Function
I get the error "Value of Type 'String' Cannot be converted to 'T'" Same with the integer part. If I cast either to an object before asigning them to retval it works but I think that would defeat my purpose and be less efficient. Any Ideas? Thanks!
It's probably a bit late, but try this:
Public Shared Function CAnyType(Of T)(ByRef UTO As Object) As T
Return CType(UTO, T)
End Function
Public Shared Function ExecuteSQLstmtScalar(Of T)(ByVal strSQL As String) As T
Dim T_ReturnValue As T
' Here we have the result of a DB query '
Dim obj As Object = "Value from DB query cmd.ExecuteScalar"
Dim strReturnValue As Object = obj.ToString();
Try
Dim tReturnType As Type = GetType(T)
If tReturnType Is GetType(String) Then
Return CAnyType(Of T)(strReturnValue)
ElseIf tReturnType Is GetType(Boolean) Then
Dim bReturnValue As Boolean = Boolean.Parse(strReturnValue)
Return CAnyType(Of T)(bReturnValue)
ElseIf tReturnType Is GetType(Integer) Then
Dim iReturnValue As Integer = Integer.Parse(strReturnValue)
Return CAnyType(Of T)(iReturnValue)
ElseIf tReturnType Is GetType(Long) Then
Dim lngReturnValue As Long = Long.Parse(strReturnValue)
Return CAnyType(Of T)(lngReturnValue)
Else
MsgBox("ExecuteSQLstmtScalar(Of T): This type is not yet defined.")
End If
Catch ex As Exception
End Try
Return Nothing
End Function
(the secrect is casting your generic result to object, then casting from type Object to template type T).
PS:
You are responsible to ensure that your code works correctly with nullable types and NOT nullable types, as well as System.DbNull.Value. For example when string is NULL and return value type is Boolean (not nullable). On a sidenote, please also note that VB Nothing is NOT equal NULL, it's equal to C#'s default(T) (e.g. System.Guid.Empty for Guid)
With a generic method, T will be of exactly one type each time. Let's say that you have code calling Example(Of Integer). Now, in your mind, replace T with Integer. The resulting method will contain these lines (amongst others).
Dim retval As Integer
If TypeOf retval Is String Then
Dim myString As String = "Hello"
retval = myString
' more code follows '
Assigning a String to an integer like that will never work. Sure, that code will also never execute, since the If-block prevents that, but the code will still not compile. (As a side not, the above code will fail to compile because the TypeOf keyword is restricted to use with reference types, but that is another story)
Typically when creating generic methods, you will want to do the same thing with whatever input you get, but in a type safe manner. If you want to have different behavior for different types of input, you are usually better off by overloading the methods instead.
retVal = (T) "Hello World!"
Do retval = Ctype(Mystring, T) or retVal = Ctype(MyInt, T)
An alternative solution is encapsulate this kind of logic in a class and use VB CallByName function:
Class Aux(Of T)
Public Value As T
Private dicc As Dictionary(Of String, Object)
Sub New()
dicc = New Dictionary(Of String, Object)
dicc.Add("system.string", "hola")
dicc.Add("system.int32", 15)
dicc.Add("system.double", 15.0)
End Sub
Public Function Test() As T
Dim typeName As String = GetType(T).ToString.ToLower
If dicc.ContainsKey(typeName) Then
CallByName(Me, "Value", CallType.Set, dicc(typeName))
End If
Return Value
End Function
Protected Overrides Sub Finalize()
MyBase.Finalize()
If Not (dicc Is Nothing) Then dicc.Clear()
dicc = Nothing
End Sub
End Class