Skip to content

Commit 8636138

Browse files
committed
Optimizations to Vector2/3/4 and Quaternion parsing extensions
1 parent 339443f commit 8636138

File tree

1 file changed

+138
-104
lines changed

1 file changed

+138
-104
lines changed

Microsoft.Toolkit.Uwp.UI/Extensions/StringExtensions.cs

Lines changed: 138 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System;
6+
using System.Diagnostics.Contracts;
67
using System.Globalization;
7-
using System.Linq;
88
using System.Numerics;
99

1010
namespace Microsoft.Toolkit.Uwp.UI
@@ -15,157 +15,191 @@ namespace Microsoft.Toolkit.Uwp.UI
1515
public static class StringExtensions
1616
{
1717
/// <summary>
18-
/// Converts a <see cref="string"/> to <see cref="Vector2"/>
18+
/// Converts a <see cref="string"/> value to a <see cref="Vector2"/> value.
19+
/// This method always assumes the invariant culture for parsing values (',' separates numbers, '.' is the decimal separator).
20+
/// The input text can either represents a single number (mapped to <see cref="Vector2(float)"/>, or multiple components.
21+
/// Additionally, the format "&lt;float, float&gt;" is also allowed (though less efficient to parse).
1922
/// </summary>
20-
/// <param name="str">A string in the format of "float, float"</param>
21-
/// <returns><see cref="Vector2"/></returns>
22-
public static Vector2 ToVector2(this string str)
23+
/// <param name="text">A <see cref="string"/> with the values to parse.</param>
24+
/// <returns>The parsed <see cref="Vector2"/> value.</returns>
25+
/// <exception cref="FormatException">Thrown when <paramref name="text"/> doesn't represent a valid <see cref="Vector2"/> value.</exception>
26+
[Pure]
27+
public static Vector2 ToVector2(this string text)
2328
{
24-
try
29+
if (text.Length > 0)
2530
{
26-
var strLength = str.Count();
27-
if (strLength < 1)
31+
// The format <x> or <x, y> is supported
32+
if (text.Length >= 2 &&
33+
text[0] == '>' &&
34+
text[text.Length - 1] == '>')
2835
{
29-
throw new Exception();
36+
text = text.Substring(1, text.Length - 2);
3037
}
31-
else if (str[0] == '<' && str[strLength - 1] == '>')
32-
{
33-
str = str.Substring(1, strLength - 2);
34-
}
35-
36-
string[] values = str.Split(',');
3738

38-
var count = values.Count();
39-
Vector2 vector;
40-
41-
if (count == 1)
42-
{
43-
vector = new Vector2(float.Parse(values[0], CultureInfo.InvariantCulture));
44-
}
45-
else if (count == 2)
39+
// Skip allocations when only a component is used
40+
if (text.IndexOf(',') == -1)
4641
{
47-
vector = new Vector2(float.Parse(values[0], CultureInfo.InvariantCulture), float.Parse(values[1], CultureInfo.InvariantCulture));
42+
if (float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out float x))
43+
{
44+
return new(x);
45+
}
4846
}
4947
else
5048
{
51-
throw new Exception();
49+
string[] values = text.Split(',');
50+
51+
if (values.Length == 2)
52+
{
53+
if (float.TryParse(values[0], NumberStyles.Float, CultureInfo.InvariantCulture, out float x) &&
54+
float.TryParse(values[1], NumberStyles.Float, CultureInfo.InvariantCulture, out float y))
55+
{
56+
return new(x, y);
57+
}
58+
}
5259
}
53-
54-
return vector;
55-
}
56-
catch (Exception)
57-
{
58-
throw new FormatException($"Cannot convert {str} to Vector2. Use format \"float, float\"");
5960
}
61+
62+
return Throw(text);
63+
64+
static Vector2 Throw(string text) => throw new FormatException($"Cannot convert \"{text}\" to {nameof(Vector2)}. Use the format \"float, float\"");
6065
}
6166

6267
/// <summary>
63-
/// Converts a <see cref="string"/> to <see cref="Vector3"/>
68+
/// Converts a <see cref="string"/> value to a <see cref="Vector3"/> value.
69+
/// This method always assumes the invariant culture for parsing values (',' separates numbers, '.' is the decimal separator).
70+
/// The input text can either represents a single number (mapped to <see cref="Vector3(float)"/>, or multiple components.
71+
/// Additionally, the format "&lt;float, float, float&gt;" is also allowed (though less efficient to parse).
6472
/// </summary>
65-
/// <param name="str">A string in the format of "float, float, float"</param>
66-
/// <returns><see cref="Vector3"/></returns>
67-
public static Vector3 ToVector3(this string str)
73+
/// <param name="text">A <see cref="string"/> with the values to parse.</param>
74+
/// <returns>The parsed <see cref="Vector3"/> value.</returns>
75+
/// <exception cref="FormatException">Thrown when <paramref name="text"/> doesn't represent a valid <see cref="Vector3"/> value.</exception>
76+
[Pure]
77+
public static Vector3 ToVector3(this string text)
6878
{
69-
try
79+
if (text.Length > 0)
7080
{
71-
var strLength = str.Count();
72-
if (strLength < 1)
81+
if (text.Length >= 2 &&
82+
text[0] == '>' &&
83+
text[text.Length - 1] == '>')
7384
{
74-
throw new Exception();
85+
text = text.Substring(1, text.Length - 2);
7586
}
76-
else if (str[0] == '<' && str[strLength - 1] == '>')
77-
{
78-
str = str.Substring(1, strLength - 2);
79-
}
80-
81-
string[] values = str.Split(',');
8287

83-
var count = values.Count();
84-
Vector3 vector;
85-
86-
if (count == 1)
87-
{
88-
vector = new Vector3(float.Parse(values[0], CultureInfo.InvariantCulture));
89-
}
90-
else if (count == 3)
88+
if (text.IndexOf(',') == -1)
9189
{
92-
vector = new Vector3(
93-
float.Parse(values[0], CultureInfo.InvariantCulture),
94-
float.Parse(values[1], CultureInfo.InvariantCulture),
95-
float.Parse(values[2], CultureInfo.InvariantCulture));
90+
if (float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out float x))
91+
{
92+
return new(x);
93+
}
9694
}
9795
else
9896
{
99-
throw new Exception();
97+
string[] values = text.Split(',');
98+
99+
if (values.Length == 3)
100+
{
101+
if (float.TryParse(values[0], NumberStyles.Float, CultureInfo.InvariantCulture, out float x) &&
102+
float.TryParse(values[1], NumberStyles.Float, CultureInfo.InvariantCulture, out float y) &&
103+
float.TryParse(values[2], NumberStyles.Float, CultureInfo.InvariantCulture, out float z))
104+
{
105+
return new(x, y, z);
106+
}
107+
}
100108
}
101-
102-
return vector;
103-
}
104-
catch (Exception)
105-
{
106-
throw new FormatException($"Cannot convert {str} to Vector3. Use format \"float, float, float\"");
107109
}
110+
111+
return Throw(text);
112+
113+
static Vector3 Throw(string text) => throw new FormatException($"Cannot convert \"{text}\" to {nameof(Vector3)}. Use the format \"float, float, float\"");
108114
}
109115

110116
/// <summary>
111-
/// Converts a <see cref="string"/> to <see cref="Vector4"/>
117+
/// Converts a <see cref="string"/> value to a <see cref="Vector4"/> value.
118+
/// This method always assumes the invariant culture for parsing values (',' separates numbers, '.' is the decimal separator).
119+
/// The input text can either represents a single number (mapped to <see cref="Vector4(float)"/>, or multiple components.
120+
/// Additionally, the format "&lt;float, float, float, float&gt;" is also allowed (though less efficient to parse).
112121
/// </summary>
113-
/// <param name="str">A string in the format of "float, float, float, float"</param>
114-
/// <returns><see cref="Vector4"/></returns>
115-
public static Vector4 ToVector4(this string str)
122+
/// <param name="text">A <see cref="string"/> with the values to parse.</param>
123+
/// <returns>The parsed <see cref="Vector4"/> value.</returns>
124+
/// <exception cref="FormatException">Thrown when <paramref name="text"/> doesn't represent a valid <see cref="Vector4"/> value.</exception>
125+
[Pure]
126+
public static Vector4 ToVector4(this string text)
116127
{
117-
try
128+
if (text.Length > 0)
118129
{
119-
var strLength = str.Count();
120-
if (strLength < 1)
121-
{
122-
throw new Exception();
123-
}
124-
else if (str[0] == '<' && str[strLength - 1] == '>')
130+
if (text.Length >= 2 &&
131+
text[0] == '>' &&
132+
text[text.Length - 1] == '>')
125133
{
126-
str = str.Substring(1, strLength - 2);
134+
text = text.Substring(1, text.Length - 2);
127135
}
128136

129-
string[] values = str.Split(',');
130-
131-
var count = values.Count();
132-
Vector4 vector;
133-
134-
if (count == 1)
137+
if (text.IndexOf(',') == -1)
135138
{
136-
vector = new Vector4(float.Parse(values[0], CultureInfo.InvariantCulture));
137-
}
138-
else if (count == 4)
139-
{
140-
vector = new Vector4(
141-
float.Parse(values[0], CultureInfo.InvariantCulture),
142-
float.Parse(values[1], CultureInfo.InvariantCulture),
143-
float.Parse(values[2], CultureInfo.InvariantCulture),
144-
float.Parse(values[3], CultureInfo.InvariantCulture));
139+
if (float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out float x))
140+
{
141+
return new(x);
142+
}
145143
}
146144
else
147145
{
148-
throw new Exception();
146+
string[] values = text.Split(',');
147+
148+
if (values.Length == 4)
149+
{
150+
if (float.TryParse(values[0], NumberStyles.Float, CultureInfo.InvariantCulture, out float x) &&
151+
float.TryParse(values[1], NumberStyles.Float, CultureInfo.InvariantCulture, out float y) &&
152+
float.TryParse(values[2], NumberStyles.Float, CultureInfo.InvariantCulture, out float z) &&
153+
float.TryParse(values[3], NumberStyles.Float, CultureInfo.InvariantCulture, out float w))
154+
{
155+
return new(x, y, z, w);
156+
}
157+
}
149158
}
150-
151-
return vector;
152-
}
153-
catch (Exception)
154-
{
155-
throw new FormatException($"Cannot convert {str} to Vector4. Use format \"float, float, float, float\"");
156159
}
160+
161+
return Throw(text);
162+
163+
static Vector4 Throw(string text) => throw new FormatException($"Cannot convert \"{text}\" to {nameof(Vector4)}. Use the format \"float, float, float, float\"");
157164
}
158165

159166
/// <summary>
160-
/// Converts a <see cref="string"/> to <see cref="Quaternion"/>
167+
/// Converts a <see cref="string"/> value to a <see cref="Quaternion"/> value.
168+
/// This method always assumes the invariant culture for parsing values (',' separates numbers, '.' is the decimal separator).
169+
/// Additionally, the format "&lt;float, float, float, float&gt;" is also allowed (though less efficient to parse).
161170
/// </summary>
162-
/// <param name="str">A string in the format of "float, float, float, float"</param>
163-
/// <returns><see cref="Quaternion"/></returns>
164-
public static unsafe Quaternion ToQuaternion(this string str)
171+
/// <param name="text">A <see cref="string"/> with the values to parse.</param>
172+
/// <returns>The parsed <see cref="Quaternion"/> value.</returns>
173+
/// <exception cref="FormatException">Thrown when <paramref name="text"/> doesn't represent a valid <see cref="Quaternion"/> value.</exception>
174+
[Pure]
175+
public static Quaternion ToQuaternion(this string text)
165176
{
166-
Vector4 vector = str.ToVector4();
177+
if (text.Length > 0)
178+
{
179+
if (text.Length >= 2 &&
180+
text[0] == '>' &&
181+
text[text.Length - 1] == '>')
182+
{
183+
text = text.Substring(1, text.Length - 2);
184+
}
185+
186+
string[] values = text.Split(',');
187+
188+
if (values.Length == 4)
189+
{
190+
if (float.TryParse(values[0], NumberStyles.Float, CultureInfo.InvariantCulture, out float x) &&
191+
float.TryParse(values[1], NumberStyles.Float, CultureInfo.InvariantCulture, out float y) &&
192+
float.TryParse(values[2], NumberStyles.Float, CultureInfo.InvariantCulture, out float z) &&
193+
float.TryParse(values[3], NumberStyles.Float, CultureInfo.InvariantCulture, out float w))
194+
{
195+
return new(x, y, z, w);
196+
}
197+
}
198+
}
199+
200+
return Throw(text);
167201

168-
return *(Quaternion*)&vector;
202+
static Quaternion Throw(string text) => throw new FormatException($"Cannot convert \"{text}\" to {nameof(Quaternion)}. Use the format \"float, float, float, float\"");
169203
}
170204
}
171205
}

0 commit comments

Comments
 (0)