diff options
Diffstat (limited to 'src/mscorlib/shared/System/Version.cs')
-rw-r--r-- | src/mscorlib/shared/System/Version.cs | 340 |
1 files changed, 155 insertions, 185 deletions
diff --git a/src/mscorlib/shared/System/Version.cs b/src/mscorlib/shared/System/Version.cs index a2140ab137..9c66d9b904 100644 --- a/src/mscorlib/shared/System/Version.cs +++ b/src/mscorlib/shared/System/Version.cs @@ -197,82 +197,118 @@ namespace System return accumulator; } - public override String ToString() + public override string ToString() => + ToString(DefaultFormatFieldCount); + + public string ToString(int fieldCount) => + fieldCount == 0 ? string.Empty : + fieldCount == 1 ? _Major.ToString() : + StringBuilderCache.GetStringAndRelease(ToCachedStringBuilder(fieldCount)); + + public bool TryFormat(Span<char> destination, out int charsWritten) => + TryFormat(destination, DefaultFormatFieldCount, out charsWritten); + + public bool TryFormat(Span<char> destination, int fieldCount, out int charsWritten) { - if (_Build == -1) return (ToString(2)); - if (_Revision == -1) return (ToString(3)); - return (ToString(4)); + if (fieldCount == 0) + { + charsWritten = 0; + return true; + } + + // TODO https://github.com/dotnet/corefx/issues/22403: fieldCount==1 can just use int.TryFormat + + StringBuilder sb = ToCachedStringBuilder(fieldCount); + if (sb.Length <= destination.Length) + { + sb.CopyTo(0, destination, sb.Length); + StringBuilderCache.Release(sb); + charsWritten = sb.Length; + return true; + } + + StringBuilderCache.Release(sb); + charsWritten = 0; + return false; } - public String ToString(int fieldCount) + private int DefaultFormatFieldCount => + _Build == -1 ? 2 : + _Revision == -1 ? 3 : + 4; + + private StringBuilder ToCachedStringBuilder(int fieldCount) { - StringBuilder sb; - switch (fieldCount) + if (fieldCount == 1) { - case 0: - return (String.Empty); - case 1: - return (_Major.ToString()); - case 2: - sb = StringBuilderCache.Acquire(); - AppendPositiveNumber(_Major, sb); - sb.Append('.'); - AppendPositiveNumber(_Minor, sb); - return StringBuilderCache.GetStringAndRelease(sb); - default: - if (_Build == -1) - throw new ArgumentException(SR.Format(SR.ArgumentOutOfRange_Bounds_Lower_Upper, "0", "2"), nameof(fieldCount)); + StringBuilder sb = StringBuilderCache.Acquire(); + AppendNonNegativeNumber(_Major, sb); + return sb; + } + else if (fieldCount == 2) + { + StringBuilder sb = StringBuilderCache.Acquire(); + AppendNonNegativeNumber(_Major, sb); + sb.Append('.'); + AppendNonNegativeNumber(_Minor, sb); + return sb; + } + else + { + if (_Build == -1) + { + throw new ArgumentException(SR.Format(SR.ArgumentOutOfRange_Bounds_Lower_Upper, "0", "2"), nameof(fieldCount)); + } - if (fieldCount == 3) - { - sb = StringBuilderCache.Acquire(); - AppendPositiveNumber(_Major, sb); - sb.Append('.'); - AppendPositiveNumber(_Minor, sb); - sb.Append('.'); - AppendPositiveNumber(_Build, sb); - return StringBuilderCache.GetStringAndRelease(sb); - } + if (fieldCount == 3) + { + StringBuilder sb = StringBuilderCache.Acquire(); + AppendNonNegativeNumber(_Major, sb); + sb.Append('.'); + AppendNonNegativeNumber(_Minor, sb); + sb.Append('.'); + AppendNonNegativeNumber(_Build, sb); + return sb; + } - if (_Revision == -1) - throw new ArgumentException(SR.Format(SR.ArgumentOutOfRange_Bounds_Lower_Upper, "0", "3"), nameof(fieldCount)); + if (_Revision == -1) + { + throw new ArgumentException(SR.Format(SR.ArgumentOutOfRange_Bounds_Lower_Upper, "0", "3"), nameof(fieldCount)); + } - if (fieldCount == 4) - { - sb = StringBuilderCache.Acquire(); - AppendPositiveNumber(_Major, sb); - sb.Append('.'); - AppendPositiveNumber(_Minor, sb); - sb.Append('.'); - AppendPositiveNumber(_Build, sb); - sb.Append('.'); - AppendPositiveNumber(_Revision, sb); - return StringBuilderCache.GetStringAndRelease(sb); - } + if (fieldCount == 4) + { + StringBuilder sb = StringBuilderCache.Acquire(); + AppendNonNegativeNumber(_Major, sb); + sb.Append('.'); + AppendNonNegativeNumber(_Minor, sb); + sb.Append('.'); + AppendNonNegativeNumber(_Build, sb); + sb.Append('.'); + AppendNonNegativeNumber(_Revision, sb); + return sb; + } - throw new ArgumentException(SR.Format(SR.ArgumentOutOfRange_Bounds_Lower_Upper, "0", "4"), nameof(fieldCount)); + throw new ArgumentException(SR.Format(SR.ArgumentOutOfRange_Bounds_Lower_Upper, "0", "4"), nameof(fieldCount)); } } + // TODO https://github.com/dotnet/corefx/issues/22616: + // Use StringBuilder.Append(int) once it's been updated to use spans internally. // - // AppendPositiveNumber is an optimization to append a number to a StringBuilder object without + // AppendNonNegativeNumber is an optimization to append a number to a StringBuilder object without // doing any boxing and not even creating intermediate string. // Note: as we always have positive numbers then it is safe to convert the number to string // regardless of the current culture as we'll not have any punctuation marks in the number - // - private const int ZERO_CHAR_VALUE = (int)'0'; - private static void AppendPositiveNumber(int num, StringBuilder sb) + private static void AppendNonNegativeNumber(int num, StringBuilder sb) { Debug.Assert(num >= 0, "AppendPositiveNumber expect positive numbers"); int index = sb.Length; - int reminder; - do { - reminder = num % 10; - num = num / 10; - sb.Insert(index, (char)(ZERO_CHAR_VALUE + reminder)); + num = Math.DivRem(num, 10, out int remainder); + sb.Insert(index, (char)('0' + remainder)); } while (num > 0); } @@ -282,104 +318,108 @@ namespace System { throw new ArgumentNullException(nameof(input)); } - Contract.EndContractBlock(); - VersionResult r = new VersionResult(); - r.Init(nameof(input), true); - if (!TryParseVersion(input, ref r)) - { - throw r.GetVersionParseException(); - } - return r.m_parsedVersion; + return ParseVersion(input.AsReadOnlySpan(), throwOnFailure: true); } - public static bool TryParse(string input, out Version result) - { - VersionResult r = new VersionResult(); - r.Init(nameof(input), false); - bool b = TryParseVersion(input, ref r); - result = r.m_parsedVersion; - return b; - } + public static Version Parse(ReadOnlySpan<char> input) => + ParseVersion(input, throwOnFailure: true); - private static bool TryParseVersion(string version, ref VersionResult result) + public static bool TryParse(string input, out Version result) { - int major, minor, build, revision; - - if ((Object)version == null) + if (input == null) { - result.SetFailure(ParseFailureKind.ArgumentNullException); + result = null; return false; } - String[] parsedComponents = version.Split('.'); - int parsedComponentsLength = parsedComponents.Length; - if ((parsedComponentsLength < 2) || (parsedComponentsLength > 4)) + return (result = ParseVersion(input.AsReadOnlySpan(), throwOnFailure: false)) != null; + } + + public static bool TryParse(ReadOnlySpan<char> input, out Version result) => + (result = ParseVersion(input, throwOnFailure: false)) != null; + + private static Version ParseVersion(ReadOnlySpan<char> input, bool throwOnFailure) + { + // Find the separator between major and minor. It must exist. + int majorEnd = input.IndexOf('.'); + if (majorEnd < 0) { - result.SetFailure(ParseFailureKind.ArgumentException); - return false; + if (throwOnFailure) throw new ArgumentException(SR.Arg_VersionString, nameof(input)); + return null; } - if (!TryParseComponent(parsedComponents[0], nameof(version), ref result, out major)) + // Find the ends of the optional minor and build portions. + // We musn't have any separators after build. + int buildEnd = -1; + int minorEnd = input.IndexOf('.', majorEnd + 1); + if (minorEnd != -1) { - return false; + buildEnd = input.IndexOf('.', minorEnd + 1); + if (buildEnd != -1) + { + if (input.IndexOf('.', buildEnd + 1) != -1) + { + if (throwOnFailure) throw new ArgumentException(SR.Arg_VersionString, nameof(input)); + return null; + } + } } - if (!TryParseComponent(parsedComponents[1], nameof(version), ref result, out minor)) + int major, minor, build, revision; + + // Parse the major version + if (!TryParseComponent(input.Slice(0, majorEnd), nameof(input), throwOnFailure, out major)) { - return false; + return null; } - parsedComponentsLength -= 2; - - if (parsedComponentsLength > 0) + if (minorEnd != -1) { - if (!TryParseComponent(parsedComponents[2], "build", ref result, out build)) + // If there's more than a major and minor, parse the minor, too. + if (!TryParseComponent(input.Slice(majorEnd + 1, minorEnd - majorEnd - 1), nameof(input), throwOnFailure, out minor)) { - return false; + return null; } - parsedComponentsLength--; - - if (parsedComponentsLength > 0) + if (buildEnd != -1) { - if (!TryParseComponent(parsedComponents[3], "revision", ref result, out revision)) - { - return false; - } - else - { - result.m_parsedVersion = new Version(major, minor, build, revision); - } + // major.minor.build.revision + return + TryParseComponent(input.Slice(minorEnd + 1, buildEnd - minorEnd - 1), nameof(build), throwOnFailure, out build) && + TryParseComponent(input.Slice(buildEnd + 1), nameof(revision), throwOnFailure, out revision) ? + new Version(major, minor, build, revision) : + null; } else { - result.m_parsedVersion = new Version(major, minor, build); + // major.minor.build + return TryParseComponent(input.Slice(minorEnd + 1), nameof(build), throwOnFailure, out build) ? + new Version(major, minor, build) : + null; } } else { - result.m_parsedVersion = new Version(major, minor); + // major.minor + return TryParseComponent(input.Slice(majorEnd + 1), nameof(input), throwOnFailure, out minor) ? + new Version(major, minor) : + null; } - - return true; } - private static bool TryParseComponent(string component, string componentName, ref VersionResult result, out int parsedComponent) + private static bool TryParseComponent(ReadOnlySpan<char> component, string componentName, bool throwOnFailure, out int parsedComponent) { - if (!Int32.TryParse(component, NumberStyles.Integer, CultureInfo.InvariantCulture, out parsedComponent)) + if (throwOnFailure) { - result.SetFailure(ParseFailureKind.FormatException, component); - return false; - } - - if (parsedComponent < 0) - { - result.SetFailure(ParseFailureKind.ArgumentOutOfRangeException, componentName); - return false; + if ((parsedComponent = int.Parse(component, NumberStyles.Integer, CultureInfo.InvariantCulture)) < 0) + { + throw new ArgumentOutOfRangeException(componentName, SR.ArgumentOutOfRange_Version); + } + return true; } - return true; + return int.TryParse(component, out parsedComponent, NumberStyles.Integer, CultureInfo.InvariantCulture) && parsedComponent >= 0; } public static bool operator ==(Version v1, Version v2) @@ -422,75 +462,5 @@ namespace System { return (v2 <= v1); } - - internal enum ParseFailureKind - { - ArgumentNullException, - ArgumentException, - ArgumentOutOfRangeException, - FormatException - } - - internal struct VersionResult - { - internal Version m_parsedVersion; - internal ParseFailureKind m_failure; - internal string m_exceptionArgument; - internal string m_argumentName; - internal bool m_canThrow; - - internal void Init(string argumentName, bool canThrow) - { - m_canThrow = canThrow; - m_argumentName = argumentName; - } - - internal void SetFailure(ParseFailureKind failure) - { - SetFailure(failure, String.Empty); - } - - internal void SetFailure(ParseFailureKind failure, string argument) - { - m_failure = failure; - m_exceptionArgument = argument; - if (m_canThrow) - { - throw GetVersionParseException(); - } - } - - internal Exception GetVersionParseException() - { - switch (m_failure) - { - case ParseFailureKind.ArgumentNullException: - return new ArgumentNullException(m_argumentName); - case ParseFailureKind.ArgumentException: - return new ArgumentException(SR.Arg_VersionString); - case ParseFailureKind.ArgumentOutOfRangeException: - return new ArgumentOutOfRangeException(m_exceptionArgument, SR.ArgumentOutOfRange_Version); - case ParseFailureKind.FormatException: - // Regenerate the FormatException as would be thrown by Int32.Parse() - try - { - Int32.Parse(m_exceptionArgument, CultureInfo.InvariantCulture); - } - catch (FormatException e) - { - return e; - } - catch (OverflowException e) - { - return e; - } - Debug.Assert(false, "Int32.Parse() did not throw exception but TryParse failed: " + m_exceptionArgument); - return new FormatException(SR.Format_InvalidString); - default: - Debug.Assert(false, "Unmatched case in Version.GetVersionParseException() for value: " + m_failure); - return new ArgumentException(SR.Arg_VersionString); - } - } - } } } |