Skip to content
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

Double(Single).Parse(string) and Double(Single).Parse(ReadOnlySpan<char) throws different exception for null #76442

Closed
TrayanZapryanov opened this issue Sep 30, 2022 · 8 comments
Labels
area-System.Runtime needs-author-action An issue or pull request that requires more info or actions from the author. question Answer questions and provide assistance, not an issue with source code or documentation.

Comments

@TrayanZapryanov
Copy link
Contributor

TrayanZapryanov commented Sep 30, 2022

    please file an issue on that with repro, that sounds like a bug

Originally posted by @krwq in #75452 (comment)

This makes migration from string to ReadOnlySpan a bit tricky.
Repro:

//Parse with null

string test = null;
		ReadOnlySpan<char> ros = test.AsSpan().Trim();
		try
		{
			double dVal = double.Parse(ros, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent | NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite, NumberFormatInfo.InvariantInfo);
		}
		catch (FormatException){}

		try
		{
			double dVal = double.Parse(test, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent | NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite, NumberFormatInfo.InvariantInfo);
		}
		catch (ArgumentNullException) { }

		//Parse with empty
		string testEmpty = "";
		ReadOnlySpan<char> rosEmpty = testEmpty.AsSpan().Trim();
		try
		{
			double dVal = double.Parse(rosEmpty, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent | NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite, NumberFormatInfo.InvariantInfo);
		}
		catch (FormatException) { }

		try
		{
			double dVal = double.Parse(testEmpty, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent | NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite, NumberFormatInfo.InvariantInfo);
		}
		catch (FormatException) { }
@dotnet-issue-labeler
Copy link

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

@ghost ghost added the untriaged New issue has not been triaged by the area owner label Sep 30, 2022
@Joe4evr
Copy link
Contributor

Joe4evr commented Sep 30, 2022

Well, ReadOnlySpan is a struct, so it can't have a true null value; instead a null string is normalized to an empty span. After that, you lose the information of whether or not to throw ArgumentNullException. (Yes, you could compare the actual address to default(ROS<char>), but that's somewhat relying on implementation details and might be adding too much complexity.)

According to the doc, making existing code throw less exceptions is in "Bucket 2: Reasonable Grey Area", which sounds like it could be acceptable if the impact is deemed low enough.

@TrayanZapryanov
Copy link
Contributor Author

If this is normal - how should migration looks like in dotnet repo ?
Should we add check for null in caller method(if possible) to mimic existing behavior, or it would be fine to live with FormatException instead of ArgumentNullException?
Or for every case we should check documentation and if it is written that some method throws ArgumentNull, only then to mimic old code ?

@Clockwork-Muse
Copy link
Contributor

ArgumentException is indication of programmer error, so anyone relying on it/hitting it is already relying on broken code....

That said, probably any of our callers should be checking/enforcing against null args anyways, if they then delegate to a Span implementation. For clarity reasons if nothing else.

@ghost
Copy link

ghost commented Oct 2, 2022

Tagging subscribers to this area: @dotnet/area-system-runtime
See info in area-owners.md if you want to be subscribed.

Issue Details
    please file an issue on that with repro, that sounds like a bug

Originally posted by @krwq in #75452 (comment)

This makes migration from string to ReadOnlySpan a bit tricky.
Repro:

//Parse with null

string test = null;
		ReadOnlySpan<char> ros = test.AsSpan().Trim();
		try
		{
			double dVal = double.Parse(ros, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent | NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite, NumberFormatInfo.InvariantInfo);
		}
		catch (FormatException){}

		try
		{
			double dVal = double.Parse(test, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent | NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite, NumberFormatInfo.InvariantInfo);
		}
		catch (ArgumentNullException) { }

		//Parse with empty
		string testEmpty = "";
		ReadOnlySpan<char> rosEmpty = testEmpty.AsSpan().Trim();
		try
		{
			double dVal = double.Parse(rosEmpty, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent | NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite, NumberFormatInfo.InvariantInfo);
		}
		catch (FormatException) { }

		try
		{
			double dVal = double.Parse(testEmpty, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent | NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite, NumberFormatInfo.InvariantInfo);
		}
		catch (FormatException) { }
Author: TrayanZapryanov
Assignees: -
Labels:

area-System.Runtime, untriaged

Milestone: -

@tannergooding
Copy link
Member

This difference is by design and has been this way for since .NET Core 2.1 when Span was first introduced.

As indicated, Span/ROSpan doesn't really have a way to expose "null" and as a value type there is no forced null checking required to get the length. Because of this, we can simply check the length directly and treat the input as "too short".

In general Span/ROSpan are similar to, but not the same as strings or arrays. Most of the functionality is identical and most of the time the string/array version forwards to some Span/ROSpan version after the relevant null check, but not always. These subtle differences are generally just around the null check and aren't something most devs need to consider.


Could you please clarify why you need to catch the exception here, let alone the exact exception?

Most exception types are indicative of errors and shouldn't be caught in regular circumstances (such as for control flow). In the case of something like the Parse APIs where you might be handling user input then TryParse is the correct alternativeas it returns bool and out T result instead that way you can determine success or failure without the cost or complexity of exceptions.

@tannergooding tannergooding added question Answer questions and provide assistance, not an issue with source code or documentation. needs-author-action An issue or pull request that requires more info or actions from the author. and removed untriaged New issue has not been triaged by the area owner labels Oct 3, 2022
@ghost
Copy link

ghost commented Oct 3, 2022

This issue has been marked needs-author-action and may be missing some important information.

@TrayanZapryanov
Copy link
Contributor Author

@tannergooding Thank you for the detailed explanation. I also think in this direction, just wanted to be sure that will not introduce any breaking change when replacing Double(Single).Parse with their counter Span/ROSpan version.
Closing this as answered.

@dotnet dotnet locked as resolved and limited conversation to collaborators Nov 12, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Runtime needs-author-action An issue or pull request that requires more info or actions from the author. question Answer questions and provide assistance, not an issue with source code or documentation.
Projects
None yet
Development

No branches or pull requests

5 participants