Call a different string method based on a flag in VB.NET - vb.net

I want to be able to call a different String method based on what is passed in a parameter. For example, my parameter is "%XYZ%". If there are percent signs on both sides of XYZ, further down in code I want to say SomeString.Contains("XYZ").
If there is a percent sign only on the left of XYZ, further down in code I want to say
SomeString.EndsWith("XYZ").
Ideally, I want something like this:
With objSearchTerms
If Not String.IsNullOrEmpty(.ProjectName) Then
If .ProjectName.StartsWith("%") AndAlso .ProjectName.EndsWith("%") Then
'MyStringMethod = Contains
ElseIf .ProjectName.EndsWith("%") Then
'MyStringMethod = StartsWith
ElseIf .ProjectName.StartsWith("%") Then
'MyStringMethod = EndsWith
Else
'MyStringMethod = Equals
End If
End If
End With
Then further down I want to be able to say:
filingList = filingRepository.GetList (Function(e) e.SERFFTrackingToFilings.Any(Function(x) x.SERFFTracking.Number.*MyStringMethod*(objTerms.TrackingNumber))
Thank you.

If you had a reference to the string that you want to run the method on, then you could assign the method to a Func<T, TResult> delegate, and invoke the delegate, something like.
Dim MyStringMethod As Func(Of String, Boolean)
'...
MyStringMethod = AddressOf(SomeString.Contains)
'...
Dim result As Boolean = MyStringMethod(SomeOtherString) 'SomeString.Contains(SomeOtherString)
But it looks like your SomeString is in an anonymous function where you may not already have a reference to it. Possible workaround:
Dim MyStringMethod As Func(Of String, String, Boolean)
'...if...
MyStringMethod = Function(a As String, b As String) a.Contains(b)
'...else if...
MyStringMethod = Function(a As String, b As String) a.StartsWith(b)
'...etc.
filingList = filingRepository.GetList (Function(e) e.SERFFTrackingToFilings.Any(Function(x) MyStringMethod(x.SERFFTracking.Number, objTerms.TrackingNumber)))

I ended up doing the following:
I created a Module:
Module StringExtensions
<Extension()>
Public Function ProcessString(ByVal strToCheck As String, ByVal strOperand As String, ByVal strWhatTocheck As String) As Boolean
Select Case strOperand
Case "Contains"
Return strToCheck.Trim.ToUpper.Contains(strWhatTocheck.Trim.ToUpper)
Case "StartsWith"
Return strToCheck.Trim.ToUpper.StartsWith(strWhatTocheck.Trim.ToUpper)
Case "EndsWith"
Return strToCheck.Trim.ToUpper.Contains(strWhatTocheck.Trim.ToUpper)
Case "Equals"
Return strToCheck.Trim.ToUpper.Equals(strWhatTocheck.Trim.ToUpper)
Case Else
Throw New ApplicationException("Can't match ProcessString(). ")
End Select
End Function
End Module
Then in the calling method I used the module like this:
Dim filingListDTO As New List(Of DTO.FilingDetailsViewModel)
Dim strNumberOperand As String = ""
Dim strProjectNumber As String = ""
With model
If Not String.IsNullOrEmpty(.ProjectNumber) Then
If .ProjectNumber.StartsWith("%") AndAlso .ProjectNumber.EndsWith("%") Then
strNumberOperand = "Contains"
ElseIf .ProjectNumber.EndsWith("%") Then
strNumberOperand = "StartsWith"
ElseIf .ProjectNumber.StartsWith("%") Then
strNumberOperand = "EndsWith"
Else
strNumberOperand = "Equals"
End If
strProjectNumber = .ProjectNumber.Replace("%", "").Trim.ToUpper
.ProjectNumber = strProjectNumber
End If
End With
filingListDTO = (From f In <<someList>>
Where f.ProjectNumber.ProcessString(strNumberOperand, strProjectNumber) _
Select f).ToList

Related

ContainsValue with Dictionary(Of String, Items)

How to know if a dictionary with multiple values contains specific value?
'Create dictionary
Dim testDictionary As New Dictionary(Of String, Items)
'Code to fill dictionary
'.......................
'.......................
'.......................
'Test if a specific value is contained in dictionary
Dim testValue as String = "TEST"
testDictionary.ContainsValue(testValue) 'This doesn't work
Public Class Items
Public Property Property1 As String
Public Property Property2 As String
Public Sub New()
End Sub
End Class
If you can define how to determine whether the dictionary contains that string, pass that logic into Enumerable.Any
Dim testValue As String = "TEST"
Dim contains = testDictionary.Any(Function(kvp) kvp.Value.Property1 = testValue OrElse kvp.Value.Property2 = testValue)
If contains Then
Dim containsEntries = testDictionary.Where(Function(kvp) kvp.Value.Property1 = testValue OrElse kvp.Value.Property2 = testValue)
End If
Since you reuse it for Any and Where, you can declare the predicate once
Dim predicate =
Function(kvp As KeyValuePair(Of String, Items))
Return kvp.Value.Property1 = testValue OrElse kvp.Value.Property2 = testValue
End Function
Dim contains = testDictionary.Any(predicate)
If contains Then
Dim containsEntries = testDictionary.Where(predicate)
End If
This is hard-coded to just these properties Property1 and Property2.
(you really don't need the Any if you want the entities; I just figured the Any answered your question "How to know if..." with a boolean)
If you want to check all public instance string properties, you can use reflection
Dim predicate =
Function(kvp As KeyValuePair(Of String, Items))
Return GetType(Items).
GetProperties(Reflection.BindingFlags.Public Or Reflection.BindingFlags.Instance).
Where(Function(pi) pi.PropertyType Is GetType(String)).
Aggregate(False, Function(pi1, pi2) pi1 Or (pi2.GetValue(kvp.Value) = testValue))
End Function
Dim containsWith = testDictionary.Any(predicate)
If containsWith Then
Dim containsEntries = testDictionary.Where(predicate)
End If

Custom function(s) in LINQ to Entities? How to write acceptable code?

Use of my simple function causes app to exit all the nested Using blocks and returns control to very outer End Using statement. But built-in function works fine. How to bypass this strange situation?
Public Shared ReadOnly GlobalSettingsKeyList As New HashSet(Of String)
Public Shared Function IsGlobalSetting(key As String) As Boolean
Return GlobalSettingsKeyList.Contains(key)
End Function
What doesn't work:
Using db = powerEntities.Open()
dim userID = 1
dim dbSettingsFound = (From setting In db.SETTINGS
Where setting.idUsers = If(IsGlobalSetting(setting.Name), Nothing, userID)
Where setting.Name.Contains(keyToMatch)) _
.ToDictionary(Function(x) x.Name, Function(y) y.Value)
End Using
What works fine:
Using db = powerEntities.Open()
dim userID = 1
dim dbSettingsFound = (From setting In db.SETTINGS
Where setting.idUsers = If(GlobalSettingsKeyList.Contains(setting.Name), Nothing, userID)
Where setting.Name.Contains(keyToMatch)) _
.ToDictionary(Function(x) x.Name, Function(y) y.Value)
End Using
{"LINQ to Entities does not recognize the method 'Boolean IsGlobalSetting(System.String)' method, and this method cannot be translated into a store expression."}
Done! Thanks to Jarekczek's answer here and his LinqExprHelper
So there is a way!
Private Shared Function GetDefaultKey(key As String) As String
If key.Contains("Station") Then
Return $"default{key.Substring("Station")}" 'my String Extension
Else
Return $"default{key}"
End If
End Function
Public Shared Function GetDefaultKeyAndCompareSetting(ByVal key As String) As Expression(Of Func(Of Settings, Boolean))
key = GetDefaultKey(key)
Return LinqExprHelper.NewExpr(Function(u As Settings) u.Name.Equals(key))
End Function
used like this...
Shared Sub Example(key As String)
...
Dim masterExpr = LinqExprHelper.NewExpr(Function(u As Settings, ByVal formatCompare As String) (formatCompare))
Dim isSameAsDefKey = masterExpr.ReplacePar("formatCompare", GetDefaultKeyAndCompareSetting(key).Body)
Dim result = (From def In db.Settings
Where def.idUsers Is Nothing).
Where(CType(isSameAsDefKey, Expression(Of Func(Of Settings, Boolean)))).FirstOrDefault
...
End Sub
...works like a charm

Use type to access properties instead of concrete instance

I'm having trouble figuring out how to ask this question (the title is not worded well), so let me start with an example using the NameOf() method, because it does something similar to what I'm trying to accomplish.
If I have a class like this:
Public Class MyClass
Public Property Foo As Bar
End Class
and then I declare an instance of it Dim instance As New MyClass, I can then use the NameOf() method in one of two ways:
' Both of these lines will print "Foo"
Console.WriteLine(NameOf(instance.Foo))
Console.WriteLine(NameOf(MyClass.Foo))
I would like to implement a method that can accept similar types of parameters, but I cannot figure out how.
Here is the (extension) method I wrote:
<Extension()>
Public Function GetPathOfProperty(Of T As Class, TProp)(myObj As T, prop As Expression(Of Func(Of TProp))) As String
Dim result As String = String.Empty
Dim foundName As String = String.Empty
Dim className As String = myObj.GetType().Name
Dim p As MemberExpression = TryCast(prop.Body, MemberExpression)
While p IsNot Nothing
If className = p.Member.DeclaringType.Name Then
foundName = className + "."
End If
result = foundName + p.Member.Name + "." + result
If Not String.IsNullOrEmpty(foundName) Then
Exit While
End If
p = TryCast(p.Expression, MemberExpression)
End While
Return IIf(String.IsNullOrEmpty(foundName), "", result.Substring(0, result.Length - 1))
End Function
Using my above example, I can use it like this:
' Prints "MyClass.Foo"
Console.WriteLine(instance.GetPathOfProperty(Function() instance.Foo))
' Prints "MyClass.Foo.SomeBarProperty"
Console.WriteLine(instance.GetPathOfProperty(Function() instance.Foo.SomeBarProperty))
I would like to create another version of this method that is not an extension method, but rather a static method that can be called like (or similar to) this:
Console.WriteLine(GetPathOfProperty(Function() MyClass.Foo) ' Prints "MyClass.Foo"
This way I'd be able to use the function without actually creating an instance of MyClass first. And this is where I need help. Since MyClass is not a static class, I'm not able to put MyClass.Foo in the function call. My first thought was to use Reflection, but I can't figure out how. Is there a parameter type I could use to allow me to do this (and if so, how would that look)? Or is this just a pipe-dream and not possible?
You can't pass in a direct reference to a property on a type, but you can cheat using lambda expressions (Function delegates) by typing the parameter to the delegate (essentially a static open instance delegate).
Here is my module to return a path reference from an instance or from a type:
Public Module PropertyExt
<Extension()>
Public Function TrimStart(Byval src As String, starter As String) As String
Return If(src.StartsWith(starter), src.Substring(starter.Length), src)
End Function
Public Function GetPathOfInstanceProperty(Of TRes)(e As Expression(Of Func(Of TRes))) As String
Dim b = TryCast(e.Body, MemberExpression)
Dim ans = ""
Do
ans = b.Member.Name.TrimStart("$VB$Local_") & "." & ans
b = TryCast(b.Expression, MemberExpression)
Loop While (b IsNot Nothing)
Return ans.TrimEnd(".")
End Function
Public Function GetPathOfTypeProperty(Of T,TRes)(e As Expression(Of Func(Of T,TRes))) As String
Dim b = TryCast(e.Body, MemberExpression)
Dim ans = ""
Do
ans = b.Member.Name.TrimStart("$VB$Local_") & "." & ans
Dim tryB = TryCast(b.Expression, MemberExpression)
If tryB Is Nothing Then
Dim tryParm = TryCast(b.Expression, ParameterExpression)
If tryParm IsNot Nothing Then
ans = tryParm.Type.Name & "." & ans
End If
End If
b = tryB
Loop While (b IsNot Nothing)
Return ans.TrimEnd(".")
End Function
End Module
Which you can use like so:
Dim cf = New CInstance()
Dim ipe = PropertyExt.GetPathOfInstanceProperty(Function() cf.Foo.SomeBarProperty)
Dim tpe = PropertyExt.GetPathOfTypeProperty(Function(i As CInstance) i.Foo.SomeBarProperty)
NOTE: VB also seems to rename local variables so I added some code to drop the $VB$Local_ prefix from names - this can be removed if not needed.
PS This seems a lot cleaner/simpler in C# ;)

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

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.

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