C# is a very powerful language; it provides a copious number of language features. Even so, Visual Basic often excels when it comes to certain capabilities being more accessible. A good case and Point is the “Like” Operator.
The Like Operator is best explained by the documentation. Essentially, it’s a stripped down regex test that is available with a simple operator. the Like Operator goes back to Early versions of Visual Basic. And yet, C# doesn’t have one.
C# does, of course, have access to the same regular Expressions library that VB.NET does, namely the System.Text.RegularExpressions namespace. But that doesn’t fit with what we want the Like operator for. So let’s see what we can do.
The easiest way to provide similar functionality is to turn the ‘Like’ Match expression into a Regular Expression. So let’s start with a simple static method that performs similarly to the Like operator:
1 2 3 4 5 |
public static bool Like(String value, String mask,RegexOptions options=RegexOptions.Multiline&RegexOptions.IgnorePatternWhitespace) { String usepattern = "^" + Regex.Escape(mask).Replace("\\*", ".*").Replace("\\?", ".") + "$"; return Regex.IsMatch(value, usepattern, options); } |
Basically it turns any asterisk characters into .* and question marks into . and adds ^ to the start and $ to the end; the ^ means to only match the start and the $ means to only match at the end. Both means the pattern has to match the entire string, which is what we want. The usage here would be something like:
1 2 3 4 |
if(Like("test.txt","*.txt")) { //code } |
But we can do better, I think. We cannot add a Like operator, but we can create a special, helper class and then overload == as well as create an explicit operator:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
public class Like { private String sPattern; public String Pattern { get { return sPattern; } private set { sPattern = value; } } private RegexOptions _Options; public RegexOptions Options { get { return _Options; } private set { _Options = value; } } public Like(String sPattern,RegexOptions pOptions=RegexOptions.Multiline) { Pattern=sPattern; Options = pOptions; } public bool Matches(String test) { if (test == null) throw new ArgumentNullException("test"); return IsLike(test, Pattern, Options); } public static explicit operator Like(String Source) { return new Like(Source); } public static bool operator ==(String first,Like second){ if (second == null) throw new ArgumentNullException("second"); return second.Matches(first); } public static bool operator !=(string First, Like second) { return !(First == second); } private static bool IsLike(String value, String mask, RegexOptions options = RegexOptions.Multiline&RegexOptions.IgnorePatternWhitespace) { String usepattern = "^" + Regex.Escape(mask).Replace("\\*", ".*").Replace("\\?", ".") + "$"; return Regex.IsMatch(value, usepattern, options); } } |
This turns the former code into this:
1 2 3 4 |
if("test.txt" == (Like)"*.txt") { //Stuff } |
Which is probably as close as we can get to an actual operator. Ideally you would simply convert your Like expression to a call to RegEx.IsMatch() and change the pattern as needed, but something like this could ease the porting of a large project from VB.NET to C#.
Update
As commenter Andrew has pointed out, this functionality can also be quite easily wrapped into a extension method on string as well, which is arguably a more straightforward and less confusing way of implementing this functionality than relying on a special cast operator anyway. Here is a class that would provide this extension method given the existence of the above class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; public static class LikeExtensions { public static bool Like(this String source,String pPattern) { return new Like(pPattern).Matches(source); } public static bool AllLike(this IEnumerable<String> source,String pPattern) { return source.All((sTest) => sTest.Like(pPattern)); } public static bool AnyLike(this IEnumerable<String> source,String pPattern) { return source.Any((sTest) => sTest.Like(pPattern)); } public static IEnumerable<String> SelectLike(this IEnumerable<String> source,String pPattern) { return from sTest in source where sTest.Like(pPattern) select sTest; } } |
I added bonus, arguably less useful extensions on IEnumerable<String> as well- less useful mostly because the Select, Any, and All linq extension methods that are used here can be used in the same manner in calling code to use the standard extension method anyway.
Have something to say about this post? Comment!
3 thoughts on “Emulating the VB.NET Like Operator in C#”
Thanks for this!
Your static method can be very easily turned into an extension method on String, which lets people use it like this:
bool showMyItem = MyInstance.MyStringProperty.Like(MyFilterString);
Hi,
The question mark don’t seem to work
if(“ABCDE_V2″ == (Like)”_V?”)
{
//it return FALSE.
}
The wildcard * works fine but the ? doesn’t.
“ABCDE_V2” Like “_V?” Returns False in Visual Basic. It doesn’t match because Like operates across the entire string, and won’t match portions of it, unlike a RegEx. So “ABC” Like “B” is False in Visual Basic in the same way that “ABC”==(Like)”B” is. This is also why your example returns false, since the pattern is expected to match across the entire string. You can use a * at the start of the pattern to make it work- All of these expressions return True:
"ABC"== (Like)"A?C"
"ABCDEFG" ==(Like)"*DE?G"
"ABCDE_V2" ==(Like)"*_V?"