I've recently been getting into using C# more, while most of my background prior to this has been in C++, and one of the things I just found myself running into was an awkward problem involving serialization. In particular, I am interfacing with another third-party (not open source and so cannot be modified) program which provides its own set of serialization routines with signatures like
public void serialize(string id, int value);
public void serialize(string id, long value);
public void serialize(string id, float value);
...
you get the idea, there's one overload for each primitive type. The trick now, though, is I want to write a wrapper on top of this that serializes a Dictionary<K, V>
where the Key (K) and the Value (V) could be any primitive types.
Now in C++, which has compile-time templates, this is pretty easy to do:
template<class K, V>
void serializeMap(const std::map<K, V> &map) {
for(std::map<K, V>::const_iterator it(map.begin()); it != map.end(); ++it) {
// ...
ThirdParty::serialize(keyId, it->first); // compiler figures which overload
ThirdParty::serialize(valueId, it->second); // compiler figures which overload
// ...
}
}
This works because templates are a code generator that generate separate code for each instantiation, so if you instantiate this for, say, an std::map<int, float>
, it will automatically figure out which of the different third-party routines need calling to make the magic happen and if someone passes something this would choke on, the compiler will handily choke when compiling the fcode. However, in C# the analogous feature, generics, are not a code generator, but rather a purely run-time feature.
And when googling around, I read this thread on something similar:
C# generics: cast generic type to value type
which basically was where someone was asking about doing a "type switch" on a generic parameter for something very similar, and they were told in the answers that basically - and very unhelpfully for someone like me, who is left burning for an alternative to be made explicit - that this was "bad design" and that even harder, this was a "really bad code smell". I could see why, but on the other hand, what is the alternative, especially given that in my case you are dealing with third-party code, to this?
public static void SerializeDictionary<K, V>(Dictionary<K, V> dict)
{
// ...
foreach(KeyValuePair<K, V> kvp in dict) {
// ...
if(typeof(K) == typeof(int)) {
ThirdParty.Serialize(keyId, (int)kvp.Key);
} else if(typeof(K) == typeof(long)) {
ThirdParty.Serialize(keyId, (long)kvp.Key);
} // ...
if(typeof(V) == typeof(int)) {
ThirdParty.Serialize(valueId, (int)kvp.Value);
} // ...
// ...
}
}
Because surely it can't be this:
public static void SerializeDictionary(Dictionary<int, int> dict)
{
// ... virtually identical code ...
}
public static void SerializeDictionary(Dictionary<int, long> dict)
{
// ... virtually identical code ...
}
// ...
public static void SerializeDictionary(Dictionary<long, int> dict)
{
// ... virtually identical code ...
}
public static void SerializeDictionary(Dictionary<long, long> dict)
{
// ... virtually identical code ...
}
// ... possibly dozens to over a hundred repeated methods ...
as, after all, wouldn't "dozens to hundreds of duplicated methods" be at least as big a "code smell" as the type switch? What, then does the proper design methodology for this case look like? What needs to be called needs to be called with the appropriate types, after all, and DRY is a code smell, too, especially with combinatorial numbers, I'd think.
Or is this an inherent limitation of C# for which there is no nice way around? Note that to me, an "ideal" solution would basically not write more methods than there are serialize calls in the 3rd-party program.