My question concerns type-checking in a chain of generic methods. Let's say I have an extension method that attempts to convert a byte array to an int, decimal, string, or DateTime.
public static T Read<T>(this ByteContainer ba, int size, string format) where T : struct, IConvertible
{
var s = string.Concat(ba.Bytes.Select(b => b.ToString(format)).ToArray());
var magic = FromString<T>(s);
return (T)Convert.ChangeType(magic, typeof(T));
}
This calls a method called FromString that translates the concatenated string to a specific type. Unfortunately, the business logic is completely dependent on the type T. So I end up with a megalithic if-else block:
private static T FromString<T>(string s) where T : struct
{
if (typeof(T).Equals(typeof(decimal)))
{
var x = (decimal)System.Convert.ToInt32(s) / 100;
return (T)Convert.ChangeType(x, typeof(T));
}
if (typeof(T).Equals(typeof(int)))
{
var x = System.Convert.ToInt32(s);
return (T)Convert.ChangeType(x, typeof(T));
}
if (typeof(T).Equals(typeof(DateTime)))
... etc ...
}
At this point, I would prefer multiple methods with the same name and different return types, something along the lines of this:
// <WishfulThinking>
private static decimal FromString<T>(string s)
{
return (decimal)System.Convert.ToInt32(s) / 100;
}
private static int FromString<T>(string s)
{
return System.Convert.ToInt32(s);
}
// </WishfulThinking>
... but I realize this is not valid, as T can't be constrained to a specific type, and without it, all of the methods will have the same conflicting signature.
Is there a feasible way to implement FromString without the excessive type-checking? Or might there be a better way to approach this problem altogether?
Or might there be a better way to approach this problem altogether?
Sure, there is one: you can make each converter into a lambda, make a dictionary of them, and use them for the conversion, like this:
private static IDictionary<Type,Func<string,object>> Converters = new Dictionary<Type,Func<string,object>> {
{typeof(int), s => Convert.ChangeType(System.Convert.ToInt32(s), typeof(int))}
, {typeof(decimal), s => Convert.ChangeType((decimal)System.Convert.ToInt32(s) / 100, typeof(decimal))}
, ... // And so on
};
public static T Read<T>(this ByteContainer ba, int size, string format) where T : struct, IConvertible {
Func<string,object> converter;
if (!Converters.TryGetValue(typeof(T), out converter)) {
throw new ArgumentException("Unsupported type: "+typeof(T));
}
var s = string.Concat(ba.Bytes.Select(b => b.ToString(format)).ToArray());
return (T)converter(s);
}
In general, if you have to write logic that must always check the type of a generic type parameter, you are not actually writing code that benefits from being generic. That being the case, and assuming the actual problem you're trying to solve is the need to convert a byte array into some predictable built-in type that it represents, I recommend you abandon this approach and use the methods in the BitConverter class.
At the point where you could determine the value of T, simply call the appropriate method on the BitConverter class.
Update: If a generic solution is necessary, I'd suggest something similar to dasblinkenlight's answer, though I would let the caller inject the converter (after all, the caller knows the requisite result type), which avoids the problem of maintaining a list of conversion functions alongside the generic method:
public static T Read<T>(this ByteContainer ba, int size, string format,
Func<string, T> converter) where T : struct, IConvertible
{
var s = string.Concat(ba.Bytes.Select(b => b.ToString(format)).ToArray());
return converter(s);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With