Introduction
I've always found the lack of a SQL LIKE operator in C# lacking. Sure - regular expressions can do the job - but the syntax is not nearly as nice as that of LIKE. Not being able to find an actual LIKE implementation by searching I've taken the time to write one. Hopefully this can make life easier for a few other people as well.
Using the code
The code provided is an extension method for the string
class. It works in the same way that Contains
, StartsWith
, and EndsWith
works.
Here is a very basic example of its usage:
if ("abc".Like("a%"))
The following notes on its parameter's syntax:
- All searches are case insensitive.
- % or * act as multi-character wildcards and allow one or more characters.
- _ (underscore) acts as a single-character wildcard
- \ (backslash) acts as an escape character and can be used to escape %, * or _. If you want to use a backslash in your search string you can use two (\\).
- [] allows multiple characters in a search. [abc] will check for any of a, b or c.
- [a-c] will check for a, b or c.
- [^a-c] will check for anything that is not a, b or c.
- [\^] will check for ^. [^\^] will check for anything that is not ^. [a\-b] will check for a, - or b.
- \[ or [[] will check for [.
The code
A few things to note before copying the code:
- Paste this in any
static
class and it'll work out of the box. - I've commented everything to make it clear what's happening with every step. If something is unclear please post and I'll improve on the comments. The remarks XML contains usage instructions.
- This isn't really all that performance friendly and does some things in clumsy ways. Feel free to improve! If you send me the new & improved code I'll update this article ;)
- I have not tested it for every use case imaginable - it works fine in every case I could think of and write a test for. A few tests are included at the end. If you do happen to find any cases where it fails or produces unexpected results please let me know so I can fix it up.
public static bool Like(this string s, string match, bool CaseInsensitive = true)
{
if (match == null || s == null)
return false;
if (CaseInsensitive)
{
s = s.ToUpperInvariant();
match = match.ToUpperInvariant();
}
int j = 0;
bool matchanymulti = false;
string multicharmask = null;
bool inversemulticharmask = false;
for (int i = 0; i < match.Length; i++)
{
if (i == match.Length - 1 && (match[i] == '%' || match[i] == '*'))
return true;
var charcheck = true;
if (match[i] == '\\')
{
i++;
if (i == match.Length)
i--;
}
else
{
if (match[i] == '%' || match[i] == '*')
{
matchanymulti = true;
continue;
}
if (match[i] == '_')
{
if (j == s.Length)
return false;
j++;
continue;
}
if (match[i] == '[')
{
var endbracketidx = match.IndexOf(']', i);
multicharmask = match.Substring(i + 1, endbracketidx - i - 1);
inversemulticharmask = multicharmask.StartsWith("^");
if (inversemulticharmask)
multicharmask = multicharmask.Remove(0, 1);
multicharmask = multicharmask.Replace("\\^", "^");
charcheck = false;
i = endbracketidx;
if (multicharmask.Length == 3 && multicharmask[1] == '-')
{
var newmask = "";
var first = multicharmask[0];
var last = multicharmask[2];
if (last < first)
{
first = last;
last = multicharmask[0];
}
var c = first;
while (c <= last)
{
newmask += c;
c++;
}
multicharmask = newmask;
}
if (endbracketidx == -1)
return false;
}
}
var matched = false;
while (j < s.Length)
{
if (charcheck && s[j] == match[i])
{
j++;
matched = true;
break;
}
if (multicharmask != null)
{
var ismatch = multicharmask.Contains(s[j]);
if (inversemulticharmask && ismatch ||
!inversemulticharmask && !ismatch)
{
if (matchanymulti)
{
j++;
continue;
}
return false;
}
j++;
matched = true;
multicharmask = null;
break;
}
if (matchanymulti)
{
j++;
continue;
}
break;
}
if (matched)
{
matchanymulti = false;
continue;
}
return false;
}
if (j < s.Length)
return false;
return true;
}
Here are some test cases:
Action<string, bool> check = (s, b) => { if (!b)
throw new ArgumentException("Like failed with the string " + s); };
check("a%", "abc".Like("a%"));
check("%c", "abc".Like("%c"));
check("%d%", !"abc".Like("%d%"));
check("%john%", "john".Like("%john%"));
check("%john%", !"johb".Like("%john%"));
check("%joh[nb]", "johb".Like("%joh[nb]"));
check("[^z]ack", "Sack".Like("[^z]ack"));
check("[\\-b^]", "^".Like("[\\^-b]"));
check("[\\^]", !"^".Like("[^\\-abc]"));
check("d_n", "dan".Like("d_n"));
Conclusion
The code provided is intended to work similar to a SQL LIKE
expression for C# strings. It also adds a few additional capabilities like using * instead of % and escaping with \. It is intended to provide nicer syntax than regular expressions (currently at the cost of some performance).
History
- 2013/06/18 - Initial publication
- 2013/06/18 #2 - Now supports case-sensitive comparison via an optional parameter and always returns false for null input strings.