Skip to content

Commit

Permalink
[core] use StringComparison.Ordinal everywhere
Browse files Browse the repository at this point in the history
Context: https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1307
Context: https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1309
Context: dotnet/runtime#43956

I was reviewing `dotnet trace` output of the .NET Podcast app:

    6.32ms Microsoft.Maui.Controls!Microsoft.Maui.Controls.ShellNavigationManager.GetNavigationState
    3.82ms Microsoft.Maui.Controls!Microsoft.Maui.Controls.ShellUriHandler.FormatUri

The bulk of this time is spent in `string.StartsWith()`, doing a
culture-aware string comparison?

    3.82ms System.Private.CoreLib!System.String.StartsWith
    2.57ms System.Private.CoreLib!System.Globalization.CultureInfo.get_CurrentCulture

This looks to be showing the cost of the 1st culture-aware comparision
plus any time making these slower string comparisons. It would be
ideal if project templates did not even hit
`CultureInfo.get_CurrentCulture`.

To solve this, let's add to the `.editorconfig` file:

    dotnet_diagnostic.CA1307.severity = error
    dotnet_diagnostic.CA1309.severity = error

And then fix all the places errors appear.

There are some complications in projects that use `netstandard2.0`:

1. For `Contains()` use `IndexOf()` instead.

2. Use `#if NETSTANDARD2_0` for `GetHashCode()` or `Replace()`.

3. Generally, `InvariantCulture` should not be used.

After these changes, the `FormatUri` method disappears completely from
the trace, and we instead get:

    2.88ms Microsoft.Maui.Controls!Microsoft.Maui.Controls.ShellNavigationManager.GetNavigationState

I suspect this saves ~3.44ms for any MAUI app startup, and a small
amount more depending on the number of string comparisions happening.
  • Loading branch information
jonathanpeppers committed Mar 1, 2022
1 parent eef07bf commit 8ac3703
Show file tree
Hide file tree
Showing 62 changed files with 204 additions and 139 deletions.
4 changes: 4 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ indent_style = tab
# https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1416
dotnet_diagnostic.CA1416.severity = none

# Code analyzers
dotnet_diagnostic.CA1307.severity = error
dotnet_diagnostic.CA1309.severity = error

# Modifier preferences
dotnet_style_require_accessibility_modifiers = never:suggestion

Expand Down
4 changes: 3 additions & 1 deletion src/BlazorWebView/src/SharedSource/QueryStringHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

#nullable enable

using System;

namespace Microsoft.AspNetCore.Components.WebView
{
internal static class QueryStringHelper
Expand All @@ -13,7 +15,7 @@ public static string RemovePossibleQueryString(string? url)
{
return string.Empty;
}
var indexOfQueryString = url.IndexOf('?');
var indexOfQueryString = url.IndexOf('?', StringComparison.Ordinal);
return (indexOfQueryString == -1)
? url
: url.Substring(0, indexOfQueryString);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public static Font ParseUIFont(string font)

// Logger.LogLine ("TEST PARSING");

if (font.Contains("font-weight: bold;"))
if (font.Contains("font-weight: bold;", StringComparison.Ordinal))
{
// Logger.LogLine ("Found Bold");
fontAttrs = FontAttributes.Bold;
Expand Down
22 changes: 11 additions & 11 deletions src/Compatibility/Core/src/Android/BorderBackgroundManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -219,17 +219,17 @@ void BorderElementPropertyChanged(object sender, PropertyChangedEventArgs e)
return;
}

if (e.PropertyName.Equals(Button.BorderColorProperty.PropertyName) ||
e.PropertyName.Equals(Button.BorderWidthProperty.PropertyName) ||
e.PropertyName.Equals(Button.CornerRadiusProperty.PropertyName) ||
e.PropertyName.Equals(VisualElement.BackgroundColorProperty.PropertyName) ||
e.PropertyName.Equals(VisualElement.BackgroundProperty.PropertyName) ||
e.PropertyName.Equals(Specifics.Button.UseDefaultPaddingProperty.PropertyName) ||
e.PropertyName.Equals(Specifics.Button.UseDefaultShadowProperty.PropertyName) ||
e.PropertyName.Equals(Specifics.ImageButton.IsShadowEnabledProperty.PropertyName) ||
e.PropertyName.Equals(Specifics.ImageButton.ShadowColorProperty.PropertyName) ||
e.PropertyName.Equals(Specifics.ImageButton.ShadowOffsetProperty.PropertyName) ||
e.PropertyName.Equals(Specifics.ImageButton.ShadowRadiusProperty.PropertyName))
if (e.PropertyName.Equals(Button.BorderColorProperty.PropertyName, StringComparison.Ordinal) ||
e.PropertyName.Equals(Button.BorderWidthProperty.PropertyName, StringComparison.Ordinal) ||
e.PropertyName.Equals(Button.CornerRadiusProperty.PropertyName, StringComparison.Ordinal) ||
e.PropertyName.Equals(VisualElement.BackgroundColorProperty.PropertyName, StringComparison.Ordinal) ||
e.PropertyName.Equals(VisualElement.BackgroundProperty.PropertyName, StringComparison.Ordinal) ||
e.PropertyName.Equals(Specifics.Button.UseDefaultPaddingProperty.PropertyName, StringComparison.Ordinal) ||
e.PropertyName.Equals(Specifics.Button.UseDefaultShadowProperty.PropertyName, StringComparison.Ordinal) ||
e.PropertyName.Equals(Specifics.ImageButton.IsShadowEnabledProperty.PropertyName, StringComparison.Ordinal) ||
e.PropertyName.Equals(Specifics.ImageButton.ShadowColorProperty.PropertyName, StringComparison.Ordinal) ||
e.PropertyName.Equals(Specifics.ImageButton.ShadowOffsetProperty.PropertyName, StringComparison.Ordinal) ||
e.PropertyName.Equals(Specifics.ImageButton.ShadowRadiusProperty.PropertyName, StringComparison.Ordinal))
{
Reset();
UpdateDrawable();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ void SetDate(DateTime date)
{
EditText.Text = date.ToShortDateString();
}
else if (Element.Format.Contains('/'))
else if (Element.Format.Contains('/', StringComparison.Ordinal))
{
EditText.Text = date.ToString(Element.Format, CultureInfo.InvariantCulture);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public abstract class TimePickerRendererBase<TControl> : ViewRenderer<TimePicker
[PortHandler]
bool Is24HourView
{
get => (DateFormat.Is24HourFormat(Context) && Element.Format == (string)TimePicker.FormatProperty.DefaultValue) || Element.Format?.Contains('H') == true;
get => (DateFormat.Is24HourFormat(Context) && Element.Format == (string)TimePicker.FormatProperty.DefaultValue) || Element.Format?.Contains('H', StringComparison.Ordinal) == true;
}

public TimePickerRendererBase(Context context) : base(context)
Expand Down
2 changes: 1 addition & 1 deletion src/Compatibility/Core/src/Android/ResourceManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ static int IdFromTitle(string title, [DynamicallyAccessedMembers(DynamicallyAcce

// When searching by reflection you would use a "_" instead of a "."
// So this accounts for cases where users were searching with an "_"
if ((id = SearchByIdentifier(name.Replace("_", "."), defType, resource, packageName)) > 0)
if ((id = SearchByIdentifier(name.Replace("_", ".", StringComparison.Ordinal), defType, resource, packageName)) > 0)
return id;

int SearchByIdentifier(string n, string d, Resources r, string p)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public NativeViewPropertyListener(string targetProperty)

public override void ObserveValue(NSString keyPath, NSObject ofObject, NSDictionary change, IntPtr context)
{
if (keyPath.ToString().Equals(TargetProperty, StringComparison.InvariantCultureIgnoreCase))
if (keyPath.ToString().Equals(TargetProperty, StringComparison.OrdinalIgnoreCase))
PropertyChanged?.Invoke(ofObject, new PropertyChangedEventArgs(TargetProperty));
else
base.ObserveValue(keyPath, ofObject, change, context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,12 +176,12 @@ void UpdateDateFromModel(bool animate)
_picker.SetDate(Element.Date.ToNSDate(), animate);

// Can't use Element.Format because it won't display the correct format if the region and language are set differently
if (string.IsNullOrWhiteSpace(Element.Format) || Element.Format.Equals("d") || Element.Format.Equals("D"))
if (string.IsNullOrWhiteSpace(Element.Format) || Element.Format.Equals("d", StringComparison.OrdinalIgnoreCase))
{
NSDateFormatter dateFormatter = new NSDateFormatter();
dateFormatter.TimeZone = NSTimeZone.FromGMT(0);

if (Element.Format?.Equals("D") == true)
if (Element.Format?.Equals("D", StringComparison.Ordinal) == true)
{
dateFormatter.DateStyle = NSDateFormatterStyle.Long;
var strDate = dateFormatter.StringFor(_picker.Date);
Expand All @@ -194,7 +194,7 @@ void UpdateDateFromModel(bool animate)
Control.Text = strDate;
}
}
else if (Element.Format.Contains("/"))
else if (Element.Format.Contains('/', StringComparison.Ordinal))
{
Control.Text = Element.Date.ToString(Element.Format, CultureInfo.InvariantCulture);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,13 +233,13 @@ void UpdateTime()
Control.Text = DateTime.Today.Add(Element.Time).ToString(Element.Format, cultureInfos);
}

if (Element.Format?.Contains('H') == true)
if (Element.Format?.Contains('H', StringComparison.Ordinal) == true)
{
var ci = new System.Globalization.CultureInfo("de-DE");
NSLocale locale = new NSLocale(ci.TwoLetterISOLanguageName);
_picker.Locale = locale;
}
else if (Element.Format?.Contains('h') == true)
else if (Element.Format?.Contains('h', StringComparison.Ordinal) == true)
{
var ci = new System.Globalization.CultureInfo("en-US");
NSLocale locale = new NSLocale(ci.TwoLetterISOLanguageName);
Expand Down
4 changes: 2 additions & 2 deletions src/Compatibility/Core/src/iOS/Renderers/WkWebViewRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ async Task<List<NSHttpCookie>> GetCookiesFromNativeStore(string url)
{
// we don't care that much about this being accurate
// the cookie container will split the cookies up more correctly
if (!cookie.Domain.Contains(domain) && !domain.Contains(cookie.Domain))
if (!cookie.Domain.Contains(domain, StringComparison.Ordinal) && !domain.Contains(cookie.Domain, StringComparison.Ordinal))
continue;

existingCookies.Add(cookie);
Expand Down Expand Up @@ -563,7 +563,7 @@ async Task DeleteCookies(List<NSHttpCookie> cookies)
foreach (var deleteme in cookies)
{
if (record.DisplayName.Contains(deleteme.Domain) || deleteme.Domain.Contains(record.DisplayName))
if (record.DisplayName.Contains(deleteme.Domain, StringComparison.Ordinal) || deleteme.Domain.Contains(record.DisplayName, StringComparison.Ordinal))
{
WKWebsiteDataStore.DefaultDataStore.RemoveDataOfTypes(record.DataTypes,
new[] { record }, () => { });
Expand Down
11 changes: 10 additions & 1 deletion src/Controls/Maps/src/Pin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,17 @@ public override int GetHashCode()
{
unchecked
{
#if NETSTANDARD2_0
int hashCode = Label?.GetHashCode() ?? 0;
hashCode = (hashCode * 397) ^ Position.GetHashCode();
hashCode = (hashCode * 397) ^ (int)Type;
hashCode = (hashCode * 397) ^ (Address?.GetHashCode() ?? 0);
#else
int hashCode = Label?.GetHashCode(StringComparison.Ordinal) ?? 0;
hashCode = (hashCode * 397) ^ Position.GetHashCode();
hashCode = (hashCode * 397) ^ (int)Type;
hashCode = (hashCode * 397) ^ (Address?.GetHashCode(StringComparison.Ordinal) ?? 0);
#endif
return hashCode;
}
}
Expand Down Expand Up @@ -106,7 +113,9 @@ public bool SendInfoWindowClick()

bool Equals(Pin other)
{
return string.Equals(Label, other.Label) && Equals(Position, other.Position) && Type == other.Type && string.Equals(Address, other.Address);
return string.Equals(Label, other.Label, StringComparison.Ordinal) &&
Equals(Position, other.Position) && Type == other.Type &&
string.Equals(Address, other.Address, StringComparison.Ordinal);
}
}
}
10 changes: 9 additions & 1 deletion src/Controls/src/Core/Accelerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,11 @@ public static Accelerator FromString(string text)
case "fn":
case "win":
modifiers.Add(modiferMaskLower);
#if NETSTANDARD2_0
text = text.Replace(modifierMask, "");
#else
text = text.Replace(modifierMask, "", StringComparison.Ordinal);
#endif
break;
}
}
Expand Down Expand Up @@ -89,7 +93,11 @@ bool Equals(Accelerator other)
/// <include file="../../docs/Microsoft.Maui.Controls/Accelerator.xml" path="//Member[@MemberName='GetHashCode']/Docs" />
public override int GetHashCode()
{
return ToString().GetHashCode();
#if NETSTANDARD2_0
return _text.GetHashCode();
#else
return _text.GetHashCode(StringComparison.Ordinal);
#endif
}

public static implicit operator Accelerator(string accelerator)
Expand Down
11 changes: 9 additions & 2 deletions src/Controls/src/Core/AnimatableKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,25 @@ public override int GetHashCode()
unchecked
{
IAnimatable target;
#if NETSTANDARD2_0
if (!Animatable.TryGetTarget(out target))
{
return Handle?.GetHashCode() ?? 0;
}

return ((target?.GetHashCode() ?? 0) * 397) ^ (Handle?.GetHashCode() ?? 0);
#else
if (!Animatable.TryGetTarget(out target))
{
return Handle?.GetHashCode(StringComparison.Ordinal) ?? 0;
}
return ((target?.GetHashCode() ?? 0) * 397) ^ (Handle?.GetHashCode(StringComparison.Ordinal) ?? 0);
#endif
}
}

protected bool Equals(AnimatableKey other)
{
if (!string.Equals(Handle, other.Handle))
if (!string.Equals(Handle, other.Handle, StringComparison.Ordinal))
{
return false;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Controls/src/Core/BindablePropertyConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo c

if (string.IsNullOrWhiteSpace(strValue))
return null;
if (strValue.Contains(":"))
if (strValue.IndexOf(":", StringComparison.Ordinal) != -1)
{
Application.Current?.FindMauiContext()?.CreateLogger<BindablePropertyConverter>()?.LogWarning("Can't resolve properties with xml namespace prefix.");
return null;
Expand Down
4 changes: 2 additions & 2 deletions src/Controls/src/Core/BindingExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ void ParsePath()

BindingExpressionPart indexer = null;

int lbIndex = part.IndexOf('[');
int lbIndex = part.IndexOf("[", StringComparison.Ordinal);
if (lbIndex != -1)
{
int rbIndex = part.LastIndexOf(']');
Expand Down Expand Up @@ -745,7 +745,7 @@ public void PropertyChanged(object sender, PropertyChangedEventArgs args)
{
if (part.IsIndexer)
{
if (name.Contains("["))
if (name.IndexOf("[", StringComparison.Ordinal) != -1)
{
if (name != string.Format("{0}[{1}]", part.IndexerName, part.Content))
return;
Expand Down
11 changes: 7 additions & 4 deletions src/Controls/src/Core/BrushTypeConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,12 @@ public GradientBrush Parse(string css)
return _gradient;
}

_parts = css.Replace("\r\n", "").Split(new[] { '(', ')', ',' }, StringSplitOptions.RemoveEmptyEntries);
#if NETSTANDARD2_0
_parts = css.Replace("\r\n", "")
#else
_parts = css.Replace("\r\n", "", StringComparison.Ordinal)
#endif
.Split(new[] { '(', ')', ',' }, StringSplitOptions.RemoveEmptyEntries);

while (_position < _parts.Length)
{
Expand Down Expand Up @@ -306,9 +311,7 @@ Point GetGradientCenter()

if (parts.Length > gradientCenterPosition)
{
var at = parts[gradientCenterPosition].Trim();

if (at.Contains("at"))
if (parts[gradientCenterPosition].IndexOf("at", StringComparison.Ordinal) != -1)
{
gradientCenterPosition++;
var directionX = gradientCenterPosition < parts.Length ? parts[gradientCenterPosition].Trim() : string.Empty;
Expand Down
2 changes: 1 addition & 1 deletion src/Controls/src/Core/ExportEffectAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public sealed class ExportEffectAttribute : Attribute
/// <include file="../../docs/Microsoft.Maui.Controls/ExportEffectAttribute.xml" path="//Member[@MemberName='.ctor']/Docs" />
public ExportEffectAttribute(Type effectType, string uniqueName)
{
if (uniqueName.Contains("."))
if (uniqueName.IndexOf(".", StringComparison.Ordinal) != -1)
throw new ArgumentException("uniqueName must not contain a .");
Type = effectType;
Id = uniqueName;
Expand Down
2 changes: 1 addition & 1 deletion src/Controls/src/Core/FontAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo c

FontAttributes attributes = FontAttributes.None;
strValue = strValue.Trim();
if (strValue.Contains(","))
if (strValue.IndexOf(",", StringComparison.Ordinal) != -1)
{ //Xaml
foreach (var part in strValue.Split(','))
attributes |= ParseSingleAttribute(part, strValue);
Expand Down
2 changes: 1 addition & 1 deletion src/Controls/src/Core/ResourceDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ object IExtendedTypeConverter.ConvertFromInvariantString(string value, IServiceP
var rootTargetPath = XamlResourceIdAttribute.GetPathForType(rootObjectType);
var assembly = rootObjectType.GetTypeInfo().Assembly;

if (value.Contains(";assembly="))
if (value.IndexOf(";assembly=", StringComparison.Ordinal) != -1)
{
var parts = value.Split(new[] { ";assembly=" }, StringSplitOptions.RemoveEmptyEntries);
value = parts[0];
Expand Down
2 changes: 1 addition & 1 deletion src/Controls/src/Core/Shell/ShellNavigationManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ public static void ApplyQueryAttributes(Element element, ShellRouteParameters qu
if (!q.Key.StartsWith(prefix, StringComparison.Ordinal))
continue;
var key = q.Key.Substring(prefix.Length);
if (key.Contains("."))
if (key.IndexOf(".", StringComparison.Ordinal) != -1)
continue;
filteredQuery.Add(key, q.Value);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Controls/src/Core/Shell/ShellNavigationState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ static Uri TrimDownImplicitAndDefaultPaths(Uri uri)
uri = ShellUriHandler.FormatUri(uri, null);

// don't trim relative pushes
if (!uri.OriginalString.StartsWith($"{Routing.PathSeparator}{Routing.PathSeparator}"))
if (!uri.OriginalString.StartsWith("//", StringComparison.Ordinal))
return uri;

string[] parts = uri.OriginalString.TrimEnd(Routing.PathSeparator[0]).Split(Routing.PathSeparator[0]);
Expand Down
13 changes: 9 additions & 4 deletions src/Controls/src/Core/Shell/ShellUriHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ internal class ShellUriHandler

internal static Uri FormatUri(Uri path, Shell shell)
{
if (path.OriginalString.StartsWith("..") && shell?.CurrentState != null)
if (path.OriginalString.StartsWith("..", StringComparison.Ordinal) && shell?.CurrentState != null)
{
var pathAndQueryString = path.OriginalString.Split(new[] { '?' }, 2);
string pathPart = pathAndQueryString[0];
Expand Down Expand Up @@ -334,7 +334,12 @@ internal static List<RouteRequestBuilder> GenerateRoutePaths(Shell shell, Uri re
var uri = ConvertToStandardFormat(shell, CreateUri(route));
if (uri.Equals(request))
{
throw new Exception($"Global routes currently cannot be the only page on the stack, so absolute routing to global routes is not supported. For now, just navigate to: {originalRequest.OriginalString.Replace("//", "")}");
#if NETSTANDARD2_0
var replaced = originalRequest.OriginalString.Replace("//", "");
#else
var replaced = originalRequest.OriginalString.Replace("//", "", StringComparison.Ordinal);
#endif
throw new Exception($"Global routes currently cannot be the only page on the stack, so absolute routing to global routes is not supported. For now, just navigate to: {replaced}");
}
}
}
Expand Down Expand Up @@ -535,7 +540,7 @@ static bool FindAndAddSegmentMatch(RouteRequestBuilder possibleRoutePath, string
var collapsedRoutes = CollapsePath(routeKey, possibleRoutePath.SegmentsMatched, true);
var collapsedRoute = String.Join(_pathSeparator, collapsedRoutes);

if (routeKey.StartsWith("//"))
if (routeKey.StartsWith("//", StringComparison.Ordinal))
{
var routeKeyPaths =
routeKey.Split(_pathSeparators, StringSplitOptions.RemoveEmptyEntries);
Expand Down Expand Up @@ -578,7 +583,7 @@ static bool FindAndAddSegmentMatch(RouteRequestBuilder possibleRoutePath, string

var collapsedLeafRoute = String.Join(_pathSeparator, CollapsePath(routeKey, leafSearch.SegmentsMatched, true));

if (routeKey.StartsWith("//"))
if (routeKey.StartsWith("//", StringComparison.Ordinal))
collapsedLeafRoute = "//" + collapsedLeafRoute;

string segmentMatch = leafSearch.GetNextSegmentMatch(collapsedLeafRoute);
Expand Down
6 changes: 3 additions & 3 deletions src/Controls/src/Core/VisualElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,10 @@ static void OnTransformChanged(BindableObject bindable, object oldValue, object
var transforms = ((string)newValue).Split(' ');
foreach (var transform in transforms)
{
if (string.IsNullOrEmpty(transform) || transform.IndexOf('(') < 0 || transform.IndexOf(')') < 0)
if (string.IsNullOrEmpty(transform) || transform.IndexOf("(", StringComparison.Ordinal) < 0 || transform.IndexOf(")", StringComparison.Ordinal) < 0)
throw new FormatException("Format for transform is 'none | transform(value) [transform(value) ]*'");
var transformName = transform.Substring(0, transform.IndexOf('('));
var value = transform.Substring(transform.IndexOf('(') + 1, transform.IndexOf(')') - transform.IndexOf('(') - 1);
var transformName = transform.Substring(0, transform.IndexOf("(", StringComparison.Ordinal));
var value = transform.Substring(transform.IndexOf("(", StringComparison.Ordinal) + 1, transform.IndexOf(")", StringComparison.Ordinal) - transform.IndexOf("(", StringComparison.Ordinal) - 1);
double translationX, translationY, scaleX, scaleY, rotateX, rotateY, rotate;
if (transformName.StartsWith("translateX", StringComparison.OrdinalIgnoreCase) && double.TryParse(value, out translationX))
bindable.SetValue(TranslationXProperty, translationX);
Expand Down

0 comments on commit 8ac3703

Please sign in to comment.