-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Should StringBuilder.Equals ignore Capacity? #27684
Comments
I think that it should not ignore capacity as a string with shorter length as the input, or the other way around should definitely not be equal and if the function was to do as you suggested then possibly a lot of not only the framework, but also 99.9999999999% of user code would break. But @safern or someone knowing the parts to that well should know for sure. |
@AraHaan I'm not saying it should ignore |
Ah, ok. 🤔 |
....Part of the problem, I guess, is that Perhaps what we want is an |
@Clockwork-Muse Yes, they are readable, but I don't think that means they have to influence equality. And yes, I think something like As for the general |
Huh, missed those overloads somehow. Ah, I was looking at the framework version, not the net-core version |
I don't know why StringBuilder.Equals(StringBuilder) checks Capacity, it does not seem what you would expect, but it appears to have been that way for some time and it is documented. I am not sure we have good enough reason to change it now, unless lots of people complain their code has a bug because they were expecting it to only compare content. @vancem any idea about it? If we don't, I think adding EqualContents is reasonable. It looks like the S.R.M clone of StringBuilder does not do it. If you want to compare content, and cannot use |
@danmosemsft - As you point out the fact that StringBuilder checks Capacity is a very hold behavior in StringBuilder. I do however, completely believe that it was and still is a mistake to have this behavior (I agree with @svick). The reason it is a mistake is because frankly Capacity is NOT something that a user has much control over (the internal implementation decides this), and thus Equals is next to useless in its current form (you can only rely on it if it returns true). Both Capacity and MaxCapacity are artifacts that we have kept for compatibility reasons. Ideally Equals does not pay attention to either of them (MaxCapacity is not quite as harmful because the user has control over it (you call a special constructor), and thus can insure that basically you never use that constructor, and thus avoid the problem). While making up a 'EqualsContent' method is a step in the right direction, this is not ideal because 'Equals' is wired into many things (e.g. Dictionary lookup) by default. It is clearly unfortunate that this doesn't 'just work'. I also note that capacity is NOT a useful property on StringBuilder. No matter what value it has, it might have a new value after any operation. It is also simply not useful. Who cares what the capacity is since the implementation changes it to what it needs anyway. In this world (where Capacity can change at the implemenation's whim), arguably the implemenation should be allowed to set the capacities of its arguments to be the same before going the equals opeations. This is of course silly, but it drives home the point that this change is really not likely to break anything that was not already pretty broken. I think this is the kind of thing that we should entertain in .NET Core. Realistically, it is a long term play. People probably won't take a dependency on it for years. It just make the conversation easier in several years from now. Bottom line: I am for it (in .NET Core). It is a trivial change and arguably is not breaking the 'contract' (since the implemenation owns Capacity), and is very unlikley to break actual user code, and makes StringBuilder.Equals actually useful. For what it is worth... |
I believe only Not sure if that makes a difference. |
Interestingly it appears in as much as 5% of apps run through the APIPort tool, although that may be simply because it's used somewhere in one common library. I'm OK with changing this. @stephentoub do you have any concerns? |
I'd rather change it than add an alternative that gets it right. Let's do it. I have my normal hesitancy that someone could take a dependency on the new behavior and then things behave incorrectly on netfx and previous releases, but that's generally true for any bug fix. |
@svick are you interested in offering a PR? |
It will also need a PR into docs. |
I agree that the most likely breakage risk is that code that works correctly on .NET Core will fail on Desktop. However, as Stephen points out, that is true of any fix in .NET Core, and should not block fixes. |
@danmosemsft Yeah, I have created the PR: dotnet/corefx#32991. |
@svick, this can now be closed, correct? |
@stephentoub There is one remaining piece of work: making sure the documentation is updated. I have just opened dotnet/dotnet-api-docs#1177 for that, so I'm closing this issue here. |
Thanks |
Thank you for spotting this @svick. |
The implementation of
StringBuilder.Equals(StringBuilder)
includes this code:This means that even if two
StringBuilder
s have the same content (i.e.sb1.ToString() == sb2.ToString()
would returntrue
), callingEquals()
on them could returnfalse
. Is this the right behavior? Would it be possible to change it? (Or is that an unacceptable breaking change?)As a motivating example, consider improving performance of comparing the text of Roslyn
SyntaxNode
s.SyntaxNode.ToString()
uses (pooled)StringBuilder
to build the returnedstring
, so it might be tempting to compare just the twoStringBuilder
, avoiding astring
allocation. But this is not guaranteed to work, because it's possible the twoStringBuilder
s will have the same contents, but differentCapacity
, in which caseEquals()
would still returnfalse
.(But note that this is just an example. I assume Roslyn wouldn't be able to use
StringBuilder.Equals
directly even if it was improved, because it has to run on older frameworks.)A concrete code example:
Because of the way
StringBuilder
growing heuristic is implemented, this will print:The text was updated successfully, but these errors were encountered: