Skip to content

Instantly share code, notes, and snippets.

@sandrock
Last active October 1, 2022 15:59
Show Gist options
  • Save sandrock/6fe3298b8ac6d9d0d9872dd811a63908 to your computer and use it in GitHub Desktop.
Save sandrock/6fe3298b8ac6d9d0d9872dd811a63908 to your computer and use it in GitHub Desktop.
C# Returns an invariant, roundtrip-safe string representation numeric values

Read me

Getting an invariant string representation of some values is very important for machine-to-machine messages.

The .NET Framework does not provide an API to do that.

Here is a collection of extension methods that will help achieve this.

More info

The "R" joke

The "roundtrip format" exists but its usage is impossible or discouraged.

namespace System
{
using System.Globalization;
// source: https://gist.github.com/sandrock/6fe3298b8ac6d9d0d9872dd811a63908
/// <summary>
/// Extension methods for common types that provide ToInvariantString() capability.
/// </summary>
public static class ToStringExtensions
{
/// <summary>
/// Returns an invariant, roundtrip-safe string representation of this value.
/// </summary>
/// <param name="value"></param>
/// <returns>an invariant, roundtrip-safe string representation</returns>
public static string ToInvariantString(this float value)
{
// Format R (roundtrip) is discouraged
// https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings#the-round-trip-r-format-specifier
// For Double values, the "R" format specifier in some cases fails to successfully round-trip the original value. For both Double and Single values, it also offers relatively poor performance. Instead, we recommend that you use the "G17" format specifier for Double values and the "G9" format specifier to successfully round-trip Single values.
return value.ToString("G9", CultureInfo.InvariantCulture);
}
/// <summary>
/// Returns an invariant, roundtrip-safe string representation of this value.
/// </summary>
/// <param name="value"></param>
/// <returns>an invariant, roundtrip-safe string representation</returns>
public static string ToInvariantString(this double value)
{
// Format R (roundtrip) is discouraged
// https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings#the-round-trip-r-format-specifier
// For Double values, the "R" format specifier in some cases fails to successfully round-trip the original value. For both Double and Single values, it also offers relatively poor performance. Instead, we recommend that you use the "G17" format specifier for Double values and the "G9" format specifier to successfully round-trip Single values.
return value.ToString("G17", CultureInfo.InvariantCulture);
}
/// <summary>
/// Returns an invariant, roundtrip-safe string representation of this value.
/// </summary>
/// <param name="value"></param>
/// <returns>an invariant, roundtrip-safe string representation</returns>
public static string ToInvariantString(this int value)
{
// Format R (roundtrip) is not available
return value.ToString("D", CultureInfo.InvariantCulture);
}
/// <summary>
/// Returns an invariant, roundtrip-safe string representation of this value.
/// </summary>
/// <param name="value"></param>
/// <returns>an invariant, roundtrip-safe string representation</returns>
public static string ToInvariantString(this long value)
{
// Format R (roundtrip) is not available
return value.ToString("D", CultureInfo.InvariantCulture);
}
/// <summary>
/// Returns an invariant, roundtrip-safe string representation of this value.
/// </summary>
/// <param name="value"></param>
/// <returns>an invariant, roundtrip-safe string representation</returns>
public static string ToInvariantString(this decimal value)
{
// Format R (roundtrip) is not available
return value.ToString(CultureInfo.InvariantCulture);
}
/// <summary>
/// Returns an invariant, roundtrip-safe string representation of this value.
/// </summary>
/// <example>"2020-01-10T14:07:04.2921606+01:00"</example>
/// <param name="value"></param>
/// <returns>an invariant, roundtrip-safe string representation</returns>
public static string ToInvariantString(this DateTime value)
{
// Format R uses the ugly RFC1123 format
// Format O uses the greatest of all ISO 8601 standard
// The "O" or "o" standard format specifier represents a custom date and time format string using a pattern that preserves time zone information and emits a result string that complies with ISO 8601.
// https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings#the-round-trip-o-o-format-specifier
return value.ToString("o");
}
/// <summary>
/// Returns an invariant, roundtrip-safe string representation of this value.
/// </summary>
/// <example>"16.00:04:15.3840000"</example>
/// <param name="value"></param>
/// <returns>an invariant, roundtrip-safe string representation</returns>
public static string ToInvariantString(this TimeSpan value)
{
// Format R (roundtrip) is not available
// The "c" format specifier, unlike the "g" and "G" format specifiers, is not culture-sensitive.
// It produces the string representation of a TimeSpan value that is invariant and that is common to all previous versions of the .NET Framework before the .NET Framework 4.
// https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-timespan-format-strings#the-constant-c-format-specifier
return value.ToString("c", CultureInfo.InvariantCulture);
}
}
}
namespace UnitTests
{
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Should;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
[TestClass]
public class ExtensionsTests
{
[TestMethod]
public void ToInvariantString_Invariability()
{
var cultures = new CultureInfo[]
{
CultureInfo.InvariantCulture,
new CultureInfo("fr-FR"),
new CultureInfo("en-US"),
};
for (int i = 0; i < cultures.Length; i++)
{
// set a problematic culture
Thread.CurrentThread.CurrentCulture = Thread.CurrentThread.CurrentUICulture = cultures[i];
// verify invariability
ToStringExtensions.ToInvariantString(int.MinValue).ShouldEqual("-2147483648");
ToStringExtensions.ToInvariantString(int.MaxValue).ShouldEqual("2147483647");
ToStringExtensions.ToInvariantString(float.MaxValue).ShouldEqual("3.40282347E+38");
ToStringExtensions.ToInvariantString(float.NaN).ShouldEqual("NaN");
ToStringExtensions.ToInvariantString(float.Epsilon).ShouldEqual("1.40129846E-45");
ToStringExtensions.ToInvariantString(double.MaxValue).ShouldEqual("1.7976931348623157E+308");
ToStringExtensions.ToInvariantString(double.NaN).ShouldEqual("NaN");
ToStringExtensions.ToInvariantString(double.Epsilon).ShouldEqual("4.9406564584124654E-324");
ToStringExtensions.ToInvariantString(decimal.MaxValue).ShouldEqual("79228162514264337593543950335");
ToStringExtensions.ToInvariantString(123456789.123456789M).ShouldEqual("123456789.123456789");
ToStringExtensions.ToInvariantString(new DateTime(2020, 08, 08, 16, 35, 59, 911, DateTimeKind.Utc)).ShouldEqual("2020-08-08T16:35:59.9110000Z");
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment