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
3 Answers 3
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
-
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\$Mathieu Guindon– Mathieu Guindon2013年09月05日 22:13:02 +00:00Commented Sep 5, 2013 at 22:13
-
1\$\begingroup\$ You are right about
ByVal
- actually I think I'll change allstring_source
toByVal string_source As String
and let VB do the implicit conversion, if any (in all likelihood aString
is being passed here anyway). \$\endgroup\$Mathieu Guindon– Mathieu Guindon2013年09月07日 00:59:22 +00:00Commented Sep 7, 2013 at 0:59
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
-
1\$\begingroup\$ Thanks for editing - it's now a much better review. :) \$\endgroup\$Toby Speight– Toby Speight2021年11月25日 07:55:16 +00:00Commented Nov 25, 2021 at 7:55
-
1\$\begingroup\$ I don't understand, every string contains the null string, so it should return True? \$\endgroup\$konijn– konijn2022年08月29日 07:58:16 +00:00Commented Aug 29, 2022 at 7:58
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