我最近開始更多地使用 C#,而在此之前我的大部分背景都是 C ,而我剛剛發現自己遇到的一件事是涉及序列化的尷尬問題。特別是,我正在與另一個第三方(不是開源的,因此無法修改)程式進行互動,該程式提供了自己的一組序列化例程,其簽名如下
public void serialize(string id, int value);
public void serialize(string id, long value);
public void serialize(string id, float value);
...
你明白了,每種原始型別都有一個多載。不過,現在的訣竅是,我想在此基礎上撰寫一個包裝器,Dictionary<K, V>將 Key (K) 和 Value (V) 可以是任何原始型別的地方序列化。
現在在具有編譯時模板的 C 中,這很容易做到:
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
// ...
}
}
這是有效的,因為模板是一個代碼生成器,它為每個實體化生成單獨的代碼,所以如果你實體化它,比如 an std::map<int, float>,它會自動找出需要呼叫哪些不同的第三方例程來實作魔法傳遞了一些會阻塞的東西,編譯器在編譯 fcode 時會很容易阻塞。但是,在 C#中,類似的特性泛型不是代碼生成器,而是純粹的運行時特性。
當谷歌搜索時,我在類似的內容上閱讀了這個帖子:
C# 泛型:將泛型型別轉換為值型別
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.
uj5u.com熱心網友回復:
這是 C# 的一個限制,目前我知道沒有令人滿意的作業。最簡單的“hack”是使用(dynamic)演員表。這樣做的缺點是你失去了編譯時的安全性和一些運行時性能。根據您的專案,這些缺點可能是可以接受的。
真正應該是編譯時錯誤現在是運行時例外。(通常這對我來說不是一個大問題,因為可能會發生無數其他例外,現在只有一個,單元測驗可以解決它)
foreach (var pair in dict)
{
ThirdParty.Serialize("keyId", (dynamic)pair.Key); // at run time I will lookup the correct overload
ThirdParty.Serialize("valueId", (dynamic)pair.Value); // at run time I will lookup the correct overload
}
因為肯定不可能是這樣的:
public static void SerializeDictionary(Dictionary<int, int> dict) { // ... virtually identical code ... }
是的,為每個可能的變化手動撰寫多載可能是性能和可讀性的最佳選擇。如果幾乎所有方法都相同,那么至少查找和替換可以更新您的代碼。這是一種代碼氣味,但無法解決。有時存在重復代碼只是嘗試使其易于管理。不同的代碼氣味比其他代碼更糟糕,高耦合比受控復制更難處理。
如果確實有大量的多載,您可以使用源生成器,但是,我覺得它們會增加更多的復雜性,但會減少。
完整示例(使用動態)
var example1 = new Dictionary<int, float>
{
{ 1, 10.10f },
{ 2, 20.20f },
{ 3, 30.30f }
};
var example2 = new Dictionary<int, long>
{
{ 1, long.MaxValue },
{ 2, long.MinValue },
{ 3, 0 }
};
var example3 = new Dictionary<int, Example>
{
{ 1, new Example{ Id = 1, Value = 101 } },
{ 2, new Example{ Id = 2, Value = 202 } },
{ 3, new Example{ Id = 3, Value = 303 } }
};
var runtimExceptionExample = new Dictionary<int, double>
{
{ 1, 10.10 },
{ 2, 20.20 },
{ 3, 30.30 }
};
SerializeDictionary(example1);
SerializeDictionary(example2);
SerializeDictionary(example3);
// Exception thrown at Runtime:
// Unhandled exception. Microsoft.CSharp.RuntimeBinder.RuntimeBinderException:
// The best overloaded method match for 'ThirdParty.serialize(string, int)' has some invalid arguments
SerializeDictionary(runtimExceptionExample); // unfortunately this cannot be flagged during compile time
static void SerializeDictionary<TKey, TValue>(Dictionary<TKey, TValue> dict)
where TKey : notnull
where TValue : notnull
{
foreach (var pair in dict)
{
ThirdParty.Serialize("keyId", (dynamic)pair.Key); // at run time I will lookup the correct overload
ThirdParty.Serialize("valueId", (dynamic)pair.Value); // at run time I will lookup the correct overload
}
}
public static class ThirdParty
{
public static void Serialize(string id, int value) => Console.WriteLine($"{id}=(int){value}");
public static void Serialize(string id, long value) => Console.WriteLine($"{id}=(long){value}");
public static void Serialize(string id, float value) => Console.WriteLine($"{id}=(float){value}");
public static void Serialize(string id, Example value) => Console.WriteLine($"{id}=(Example){{{value.Id}, {value.Value}}}");
}
public class Example {
public int Id { get; set; }
public int Value { get; set; }
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/437869.html
