10
\$\begingroup\$

Consider the following:

If myString = "abc" Or myString = "def" [...] Or myString = "xyz" Then

In C# when myString == "abc" the rest of the conditions aren't evaluated. But because of how VB works, the entire expression needs to be evaluated, even if a match is found with the first comparison.

Even worse:

If InStr(1, myString, "foo") > 0 Or InStr(1, myString, "bar") > 0 [...] Then

I hate to see these things in code I work with. So I came up with these functions a while ago, been using them all over the place, was wondering if anything could be done to make them even better:

StringContains is used like If StringContains("this is a sample string", "string"):

Public Function StringContains(string_source, find_text, Optional ByVal caseSensitive As Boolean = False) As Boolean
 'String-typed local copies of passed parameter values:
 Dim find As String, src As String
 find = CStr(find_text)
 src = CStr(string_source)
 If caseSensitive Then
 StringContains = (InStr(1, src, find, vbBinaryCompare) <> 0)
 Else
 StringContains = (InStr(1, src, find, vbTextCompare) <> 0)
 End If
End Function

StringContainsAny works in a very similar way, but allows specifying any number of parameters so it's used like If StringContainsAny("this is a sample string", false, "foo", "bar", string"):

Public Function StringContainsAny(string_source, ByVal caseSensitive As Boolean, ParamArray find_strings()) As Boolean
 'String-typed local copies of passed parameter values:
 Dim find As String, src As String, i As Integer, found As Boolean
 src = CStr(string_source)
 For i = LBound(find_strings) To UBound(find_strings)
 find = CStr(find_strings(i))
 If caseSensitive Then
 found = (InStr(1, src, find, vbBinaryCompare) <> 0)
 Else
 found = (InStr(1, src, find, vbTextCompare) <> 0)
 End If
 If found Then Exit For
 Next
 StringContainsAny = found
End Function

StringMatchesAny will return True if any of the passed parameters exactly matches (case-sensitive) the string_source:

Public Function StringMatchesAny(string_source, ParamArray find_strings()) As Boolean
 'String-typed local copies of passed parameter values:
 Dim find As String, src As String, i As Integer, found As Boolean
 src = CStr(string_source)
 For i = LBound(find_strings) To UBound(find_strings)
 find = CStr(find_strings(i))
 found = (src = find)
 If found Then Exit For
 Next
 StringMatchesAny = found
End Function
asked Sep 5, 2013 at 12:12
\$\endgroup\$

3 Answers 3

11
\$\begingroup\$

My 2 cents,

the first function seems fine, you could make it a little DRYer by just setting the compareMethod in your if statement and then have only 1 complicated line of logic. And if you are doing that, you might as well put the Cstr's there.

Public Function StringContains(haystack, needle, Optional ByVal caseSensitive As Boolean = False) As Boolean
 Dim compareMethod As Integer
 If caseSensitive Then
 compareMethod = vbBinaryCompare
 Else
 compareMethod = vbTextCompare
 End If
 'Have you thought about Null?
 StringContains = (InStr(1, CStr(haystack), CStr(needle), compareMethod) <> 0)
End Function

Notice as well that I love the idea of searching for needles in haystacks, I stole that from PHP.

For StringContainsAny, you are not using the code you wrote for StringContains, you repeat it. If you were to re-use the first function, you could do this:

Public Function StringContainsAny(haystack, ByVal caseSensitive As Boolean, ParamArray needles()) As Boolean
 Dim i As Integer
 For i = LBound(needles) To UBound(needles)
 If StringContains(CStr(haystack), CStr(needles(i)), caseSensitive) Then
 StringContainsAny = True
 Exit Function
 End If
 Next
 StringContainsAny = False 'Not really necessary, default is False..
End Function

For the last one I wanted to you consider passing values that you will convert as ByVal, since you are going to make a copy anyway of that variable.

Public Function StringMatchesAny(ByVal string_source, ParamArray potential_matches()) As Boolean
 string_source = CStr(string_source)
 ... 'That code taught me a new trick ;)
End Function
answered Sep 5, 2013 at 20:58
\$\endgroup\$
2
  • 1
    \$\begingroup\$ +1 for finding a needle in a haystack! Love it! ...and for reusing StringContains. However your take at the 3rd one changes the semantics. It should be a strict equality comparison (the needle equals anything in the stack). \$\endgroup\$ Commented Sep 5, 2013 at 22:13
  • 1
    \$\begingroup\$ You are right about ByVal - actually I think I'll change all string_source to ByVal string_source As String and let VB do the implicit conversion, if any (in all likelihood a String is being passed here anyway). \$\endgroup\$ Commented Sep 7, 2013 at 0:59
3
\$\begingroup\$

I came here after reading a related post in the retailcoder blog, Enhancing VBA String Handling. Take into account that in the InStr function if String2 is an empty string then the returned value is the Start position (different from 0), so the function may erroneously return True in that case. Below the way I implement it with that consideration.

'@Description "Returns True if found an ocurrence of one string within another. False otherwise."
Public Function Contains(ByVal StringToLookIn As String, _
 ByVal StringToLookFor As String, _
 Optional ByVal CaseSensitive As Boolean = False) _
As Boolean
 If StringToLookFor <> vbNullString Then
 Dim ComparisonMethod As VbCompareMethod
 If CaseSensitive Then
 ComparisonMethod = vbBinaryCompare
 Else
 ComparisonMethod = vbTextCompare
 End If
 Contains = CBool(InStr(1, StringToLookIn, StringToLookFor, ComparisonMethod))
 End If
End Function
answered Nov 24, 2021 at 0:31
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Thanks for editing - it's now a much better review. :) \$\endgroup\$ Commented Nov 25, 2021 at 7:55
  • 1
    \$\begingroup\$ I don't understand, every string contains the null string, so it should return True? \$\endgroup\$ Commented Aug 29, 2022 at 7:58
3
\$\begingroup\$

The lack of null handling and the lack of a single-variable StringMatches function were bothering me. If StringContainsAny calls StringContains, shouldn't StringMatchesAny call StringMatches?

I went through each method to implement a couple changes accordingly, taking into account the previous reviews.

StringContains now handles null values using the Coalesce function available here. Note that passing parameters "0", 0, Null, and "" all evaluate to the same thing here and will return True if compared to one another. That may or may not be desirable. If you're just working within the world of strings and won't have any other data types, this only causes a problem between "0" and "", which you could address by providing some unique string as the optional value_when_null parameter to Coalesce. You then need to be very sure that string won't appear in your source_string or find_string, though! I've provided NullValue, which works in my case - but it might not work in yours. To really address this problem you'd need a bit more involved handling than I'm willing to dedicate to a task like this.

Public Function StringContains(ByVal source_string, ByVal find_string, Optional ByVal bCaseSensitive As Boolean = False) As Boolean
 
 Dim compareMethod As Integer
 If bCaseSensitive Then
 compareMethod = vbBinaryCompare
 Else
 compareMethod = vbTextCompare
 End If
 
 Dim sFinalSource As String, sFinalFind As String
 sFinalSource = CStr(Coalesce(source_string, "NullValue"))
 sFinalFind = CStr(Coalesce(find_string, "NullValue"))
 
 StringContains = (InStr(1, sFinalSource, sFinalFind, compareMethod) <> 0)
End Function

This nicely carries through to StringContainsAny with barely any need for change:

Public Function StringContainsAny(ByVal source_string, ByVal bCaseSensitive As Boolean, ParamArray find_strings()) As Boolean
 
 Dim i As Integer
 
 For i = LBound(find_strings) To UBound(find_strings)
 If StringContains(source_string, find_strings(i), bCaseSensitive) Then
 StringContainsAny = True
 Exit Function
 End If
 Next i
End Function

Here is the implementation of StringMatches:

Public Function StringMatches(ByVal string_source, ByVal match_string) As Boolean
 StringMatches = (CStr(Coalesce(string_source, "NullValue")) = CStr(Coalesce(string_source, "NullValue")))
End Function

That leaves just StringMatchesAny, which has been simplified a touch by the use of StringMatches:

Public Function StringMatchesAny(ByVal string_source, ParamArray match_strings()) As Boolean
 
 Dim i As Integer, bMatch As Boolean
 
 For i = LBound(find_strings) To UBound(find_strings)
 bMatch = StringMatches(string_source, match_strings(i))
 If bMatch Then Exit For
 Next
 StringMatchesAny = bMatch
End Function
Toby Speight
87.7k14 gold badges104 silver badges325 bronze badges
answered Aug 18, 2022 at 21:40
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.