Reputation: 21292
I'm working on a project that requires string representations of integers, but using odd numeric bases that are not supported natively by the .NET Framework (to my knowledge) - such as base36, 62, 64, etc.
We decided to write an overall string conversion system that will work with any numeric base, since it's a simple enough operation.
Later on, we want to create a custom IFormatProvider/ICustomFormatter to make this easier to use down the road.
But first, we want to iron out the conversion process itself, in the form of some static methods that perform the conversions and return some basic results.
Once we get this working, we'll put the IFormatProvider wrappers in place.
It's been a while since I've worked with C# generics, and I can't remember how to make the compiler happy with this operation.
I'd prefer to create a private static generic ConvertInteger
method, which I can then invoke from public static Convert
methods to help enforce strong typing without making it messy to use.
One main reason for this setup is to avoid issues with the sign bit, when converting signed values vs. unsigned values.
Right now we have public static convert
methods for long
and ulong
to help avoid conversion issues between signed and unsigned values.
In the future, assuming we can get the private static generic method to work, we'd like to expand the public methods to include int, uint, short, ushort, byte, and sbyte as explicit implementations, to help with performance when executing this method on large batches of values of varying integer sizes.
This is where the generic method design will come in handy, so we don't have to duplicate the exact same code over and over again (makes testing and debugging much simpler too).
The problem I'm running into is, the compiler doesn't allow me to perform the comparison or math operations using the generic types, since it doesn't know how to perform the operations before the generic type parameter values are provided.
(This is something that really irks me, as a C++ developer primarily, since C++ solves this problem by simply refraining from trying to interpret the generic code until after the type parameter values are provided.)
What do I need to do to satisfy the compiler with this generic method design?
I've provided the code below, comments indicating the specific compile errors.
public static class NumericStringConverter
{
private static readonly string[] StandardDigits = new string[]
{
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
"_", "-"
};
private static string ConvertInteger<T>(T Value, T Base)
{
if (Base < 2) // error here: "Operator '<' cannot be applied to operands of type 'T' and 'int'"
throw new ArgumentOutOfRangeException("Base", Base, "The NumericStringConverter.Convert(Value, Base) method was called, with the Base parameter set to a value less than 2.");
if (Base > 64) // error here: "Operator '>' cannot be applied to operands of type 'T' and 'int'"
throw new ArgumentOutOfRangeException("Base", Base, "The NumericStringConverter.Convert(Value, Base) method was called, with the Base parameter set to a value greater than 64.");
if (Value == 0) // error here: "Operator '==' cannot be applied to operands of type 'T' and 'int'"
return StandardDigits[0];
string strResult = "";
bool IsValueNegative = (Value < 0); // error here: "Operator '<' cannot be applied to operands of type 'T' and 'int'"
while (Value != 0) // error here: "Operator '!=' cannot be applied to operands of type 'T' and 'int'"
{
strResult = strResult.Insert(0, StandardDigits[Math.Abs(Value % Base)]); // error here: "Operator '%' cannot be applied to operands of type 'T' and 'T'"
Value /= Base; // error here: "Operator '/=' cannot be applied to operands of type 'T' and 'T'"
}
if (IsValueNegative)
strResult = strResult.Insert(0, "-");
return strResult;
}
public static string Convert(long Value, long Base)
{
return ConvertInteger<long>(Value, Base);
}
public static string Convert(ulong Value, ulong Base)
{
return ConvertInteger<ulong>(Value, Base);
}
public static T Convert<T>(string Value, T Base)
{
return default(T); // TODO: convert a string back into an integer value.
}
}
Upvotes: 4
Views: 2121
Reputation: 1690
Great news: there is now a solution for this in .NET 6 and C# 10, cf. https://devblogs.microsoft.com/dotnet/preview-features-in-net-6-generic-math/#generic-math
In order to perform mathematical operations, you could accept any numerical type INumber<T>
and specify the desired operators, e.g.:
public T Adding<T>(T a, T b)
where T : INumber<T>
where T : IAdditionOperators<T, T, T>
{
return a + b;
}
Note: At the time of my answer, this feature was only a preview. Microsoft will keep this for the final version of .NET 6 as they still want to allow breaking chances. To use the feature, preview features must be enabled in the project configuration:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<EnablePreviewFeatures>true</EnablePreviewFeatures>
<LangVersion>preview</LangVersion>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Runtime.Experimental" Version="6.0.0-preview.7.21377.19" />
</ItemGroup>
</Project>
Upvotes: 4
Reputation: 152556
Since you can't define a generic constraint that only allows integer types, I would just have a method that works for longs
and an overload for int
:
private static string ConvertInteger(long Value, byte Base)
{
if (Base < 2)
throw new ArgumentOutOfRangeException("Base", Base, "The NumericStringConverter.Convert(Value, Base) method was called, with the Base parameter set to a value less than 2.");
if (Base > 64)
throw new ArgumentOutOfRangeException("Base", Base, "The NumericStringConverter.Convert(Value, Base) method was called, with the Base parameter set to a value greater than 64.");
if (Value == 0)
return StandardDigits[0];
string strResult = "";
bool IsValueNegative = (Value < 0);
while (Value != 0)
{
strResult = strResult.Insert(0, StandardDigits[Math.Abs(Value % Base)]);
Value /= Base;
}
if (IsValueNegative)
strResult = strResult.Insert(0, "-");
return strResult;
}
public static string Convert(int Value, byte Base)
{
return ConvertInteger((long)Value, Base);
}
public static string Convert(ulong Value, byte Base)
{
return ConvertInteger((long)Value, Base); // check for overflow?
}
Upvotes: 4