Stop Using ToLower() for Comparisons! The Fast, Correct Way in C#
Transcript 6 topics
Stop using ToLower
0:00Are you still using too upper and too lower for your string comparisons? If so, I've got some tough love for you. It's time to stop. It allocates new strings, it's slower and depending on your culture, it could just flat out lie to you. But let's show a better way and maybe even talk about how it affects EF Core, SQL Server collation, and even run some benchmarks to show that it really matters and we just don't care about font casing.
Let's take a look at what we're actually talking about here. I'm gonna go into .net new console, and then I'm gonna open up program CS, get rid of that. And we'll do like var, did you subscribe? Uh-huh. Equals I subscribed.
Looks like an interface, but it's not. And then we can just console dot writeline. We'll say, did you subscribe dot to lower? And then let's console writeline. We'll do, did you subscribe dot to lower equals I subscribed.
Pretty straightforward code. Right? And if we dot net run that, what happens? We should get the string. Yep.
And true. Exactly what we would expect. So what's the problem, Michael? Well, number one is string allocation. Every time you use to upper or to lower, it's actually creating a new string.
Now once or twice, who cares? But if you've got this in a loop, your garbage collector is gonna really start hating you. And beyond that, if we're using a different culture, it's just gonna lie sometimes. I'll show you what I mean. Let's add a different culture to this code.
Let's say thread dot current thread dot current culture, I think it is, equals new culture info, and we'll say t r dash t r for Turkish. And if I do that, I'm gonna need, like, using I think it's a system globalization, something like that to access culture info. And if I do that, now let's dot net run it. And what do we get? I subscribed false.
Why is that? What happened? Well, in Turkish and a capital I lowercases differently. It's not an I anymore. It's whatever that thing is.
I'm sure there's a name for it, but I don't know it. But it means that our strings don't match.
StringComparison basics
2:47So what's a better way? We can solve all of this with string comparison. Let's update our code to use it instead. So we'll just come in and strip all that out and we'll say string dot equals. And we'll say, did you subscribe variable?
We'll hard code I subscribed because I'm sure you did. And then we'll do string comparison dot ordinal ignored case. Close out those, give me some X rays over there. Okay. And just for grins and giggles to make this easier to read, let's break this down on some lines so you can see kind of the pattern.
It's like a and b then the string comparison. And if we.net run that, what happens? We should get I subscribed. Notice that I subscribed is still in that Turkish, but it was able to compare that correctly ignoring culture. And also in this case, the case differences.
I didn't have to lower, did you subscribe because of that ignore case. But we need to think about string comparison like we do our favorite steak restaurant. We don't just walk in and be like, give me meat. No. We gotta choose the one that's right for us.
Right? Maybe we prefer filet mignon or a New York strip or a rib eye. Same applies to string comparison whether we want ordinal, ordinal, ignore case, all that kind of stuff. Let's go through them real quick. If we took off ignore case, we have ordinal.
Ordinal is the fastest byte wise comparison. It's great for comparing keys or tokens or identifiers, even file names on Windows. That's what you're gonna want. But if you add ignore case, what do you get? Well, as you can imagine, the same thing as ordinal, but it ignores the case.
Right? Next up would be say current culture. So if we modified that and said current culture, what we're gonna get there is a comparison that actually takes the culture into account. So are comparing, the I that gets changed from English to Turkish, that would still return a false there because it's looking at a specific culture. Why would you ever want that?
Well, if you're doing a UI, you'd actually want to think about the the culture for that user and what they're seeing. So in that sense, you do want it. There's also the current culture ignore case that does the exact same thing. It's culture sensitive, but it ignores case. Now all of these have an ignore case variant on it that basically just adds case insensitivity.
Insensitivity? Yeah, I said that right. Okay. But next up is invariant culture. And that's a stable culture agnostic option.
It's good for logs and persistent data that needs to be consistent everywhere. And we would just do invariant culture or add on at the end of it, ignore case. But let's see a couple of these in action. For instance, I'm gonna copy in some code and you'll see why, because I cannot be bothered to remember how to type those accents. But I've got a super fancy word and a semi fancy word.
Basically just different casings and different accents on them, right? But when I compare those, I'm gonna hit save and I'll dot net run. What happens? They both may be true, but which one should be right for you? Well, it depends on your intent.
If this is for something a user's gonna see or a sort that the user's gonna see, you're gonna probably want the current culture version. But if this is for logs or something on the back end, ordinal is gonna be faster and it's probably the jam you wanna run with. But what about other string methods? Things like starts with and ends with. You are in luck, my friend.
Let's look at that. Let's say we have var status equals I liked this video because who wouldn't?
StartsWith and EndsWith
6:38And then we wanna say console dot writeline. We'll say status dot starts with I liked, and then we'll say string comparison dot ordinal ignore case. Now if we dot net run that, true because we ignored case there so it's just fine. Right? What about ends with?
You got it. If you guessed that it also has it, it wasn't much of a guess because I already told you it did. But if I said test here and run that, we should get a true and a false because clearly I typed in the wrong thing in the last one. Okay. Well, thanks for the info Michael, but not so fast my friend.
There are other things besides if statements that need to know this. For instance, dictionary sets or lookups can sometimes benefit from having a compare built into them. Let's see what I mean. Let's say we have a dictionary.
Case-insensitive collections
7:54Let's say var comments equals new dictionary of string string, and then let's pass in string comparer dot ordinal case no. Ordinal ignore case. And then we'll add in let's add a record onto it and say, Mike says nice beard. Just one beard. Thanks.
Alright. And then if we said something like console writeline, let's go comments dot contains key. In this case, I'm gonna go all caps. What happens? Let's clear this, dot net run it again.
What happens? We get true. It does contain that key even though they had different casings because I specified ignore case on ordinal. But the same is true for things like a hash set. So if we refactored this into a hash set and it would just be a string and then change this to be, let's say C sharp brits and then come down here.
Well, let's leave it blank. It should be false, right? It doesn't have contains keys. It would have contains and let's dot net run.
What happens? False.
EF Core collations
9:29Works like a champ. But wait, there's more. Remember when I said we would get to EF Core? Well, we're there. If you're using EF Core with SQL Server, probably you're using the default collection, which is often case insensitive, which means any query you send is gonna have case insensitive insensitivity.
I can't say it right two times straight built in. So if I was to say var filter equals Michael, and then I said var user equals d b db dot users dot where you this IntelliSense is killing me. You dot username equals filter. By default, I'm gonna get case insensitivity. It's really a struggle.
By default, if I'm using that default, compare that collection. But if I'm not using that, I may need to specify the collations. So I could come in and say, bring in using Microsoft. EntityFramework Core, which I don't have installed. There's no database behind this.
So I don't know why I'm wasting my time showing you that using statement because it's not installed. We're not gonna run this code. But if I were, I could say ef dot, what is it functions dot collate. And then I would specify u dot username, I believe here. Yeah.
Yeah. Yeah. Yeah. And then I would have to type in my phrase like Latin one. Forget the long strings.
I can't even spell it. You see that? Look at this. See what I'm talking about? Like, you need to go look that string up, but basically you just specify the collation.
And then I guess that kind of last option would be to use something like, like, so like functions. Like, and you could say you. Display name and then say filter or whatever your text is gonna be there. Probably not the best option, but it's out there. But regardless of which one of these options you use, the key point to remember is don't use to lower in your link statements.
Can it handle it? Sure it can. It'll actually turn it into a lower function in SQL. But that means that that function is gonna get called for every row in your result set, which is just maddening for me. Don't do it.
It makes me sad. And then finally, I promised you that we weren't doing this just because I like uppercase or lowercase fonts. We don't really care about that here. It's all about performance and making our code more gooder.
Benchmarking comparisons
12:21So let's run some benchmarks to show the difference in two upper and two lower versus string comparison. I'm gonna paste in some good old fashioned benchmark.net test. We probably need to go .net add package benchmark. I've got my history already good. Add that package, Clear us out so we can run this.
Let's run through this test real quick and show you what it's gonna do. It's basically gonna run a thousand iterations with that constant string of I subscribed. Did you really? Thank you so much. It means so much to me.
I I appreciate that. We're gonna compare it with string equals with or no ignore case, and we're gonna compare it with to lower equals that lower case. With that, let's.net run it. This is gonna take a minute, but not too long, I don't think. It helps when you save the file first.
Pro tip for you, you just came here for the .net magic and I just gave you the secret. Save the file before you run it. What's happening? Oh, we have to run it. That's right for .net.
We got we gotta run it in release mode because we don't want the debug tests. We wanna see real world this mug running hardcore. What's the results? Look at it go, look at it go. What's our results?
Tell me now. Look at this, string equals versus too lower. All you need to know is the lower number, the better. And you can tell that that comparison is so much more efficient than to upper and to lower. I mean, allocating strings is expensive business.
Okay. And now we know not to use to upper and to lower in our string comparison. Use our string comparison. It's almost like it was named that way so that we would use it for the thing. Yeah.
If you've got horror stories from using two upper and two lower and memory utilization, all that, I love to hear about that stuff. So let me know. Otherwise, I'll catch you next time.
Sign in to join in. Reading needs nothing.