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
\$\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
\$\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\$ 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\$ 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
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Thanks for editing - it's now a much better review. :) \$\endgroup\$ 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
    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
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.