2011-11-14 55 views
6

我正在編寫一個自定義序列化程序的結構類型與協議我不能改變互操作。我使用反射來提取結構成員值並將它們寫入BinaryWriter。它僅用於支持它們的基本類型和數組。未知類型的序列化的動態轉換

if  (fi.FieldType.Name == "Int16") bw.Write((Int16)fi.GetValue(obj)); 
else if (fi.FieldType.Name == "UInt16") bw.Write((UInt16)fi.GetValue(obj)); 
else if (fi.FieldType.Name == "Int32") bw.Write((Int32)fi.GetValue(obj)); 
else if (fi.FieldType.Name == "UInt32") bw.Write((UInt32)fi.GetValue(obj)); 
else if (fi.FieldType.Name == "Int64") bw.Write((Int64)fi.GetValue(obj)); 
else if (fi.FieldType.Name == "UInt64") bw.Write((UInt64)fi.GetValue(obj)); 
else if (fi.FieldType.Name == "Single") bw.Write((float)fi.GetValue(obj)); 
else if (fi.FieldType.Name == "Double") bw.Write((double)fi.GetValue(obj)); 
else if (fi.FieldType.Name == "Decimal") bw.Write((decimal)fi.GetValue(obj)); 
else if (fi.FieldType.Name == "Byte") bw.Write((byte)fi.GetValue(obj)); 
else if (fi.FieldType.Name == "SByte") bw.Write((sbyte)fi.GetValue(obj)); 
else if (fi.FieldType.Name == "String") bw.Write((string)fi.GetValue(obj)); 

顯然,這是醜陋的,當我想要做同樣的事情與這些類型的數組過它會變得更加難看。

將是非常好的是什麼,如果我可以做這樣的事情:

bw.Write((fi.FieldType) fi.GetValue(obj)); 

然後做類似這樣的事情對數組。

任何想法?

+0

+1對於這個問題,我一直在尋找一個堅實的方法來做到這一點。 –

+0

什麼是fi的類型? – drdwilcox

+0

如果醜陋的代碼最終成爲唯一的選擇,我通常使用T4模板來避免這種場景,以避免愚蠢的錯誤,並讓Visual Studio自動爲我生成所有代碼。你只需要一個類型列表來迭代或類似的東西。 – mellamokb

回答

4

您可以使用反射調用的Write

public static void WriteField(BinaryWriter bw, object obj, FieldInfo fieldInfo) 
{ 
    typeof(BinaryWriter) 
     .GetMethod("Write", new Type[] { fieldInfo.FieldType }) 
     .Invoke(bw, new object[] { fieldInfo.GetValue(obj) }); 
} 
+0

這看起來可能會起作用。我會放棄它。 – Polynomial

+0

@Polynomial注意到它會慢得多......反射速度很慢。不過,根據你的情況,它仍然可能**速度夠快。 –

+0

太棒了。作品絕對完美! :D – Polynomial

2

這段代碼實際上並不醜陋......它只是重複的。但它實際上很乾淨,簡短並且很容易理解。如果你有一百萬種不同的類型來解決這個問題,那只是一件事,但只有有限的幾個。

如果你能夠做你想做的事情,如果它有問題或者需要做更多的事情而另一個程序員可能不理解它,那麼很難維護......或者你可能已經忘記了你做了什麼,並且必須重新學習它。

通過這樣做,你將有: - 增加了更多的開發時間 - 減少可讀性 - 減少速度 - 增加維修

有時候我們喜歡採取過於簡單的問題,使他們更具挑戰性。但通常,良好的商業代碼只是普通,乏味的代碼。

+0

至於「它只是repetitve」,它得到這麼多可怕的時候我得考慮'T []','名單','字典',以及反序列化了。 – Polynomial

+0

我明白了。我認爲雅各布的回答是一個非常簡單的答案......如果你想採取這種方式,我會選擇vcsJones解決方案。 –

2

如果您想簡化它,您可以使用表達式來動態地進行正確的調用。

//Cache the generated method for re-use later, say as a static field of dictionary. It shouldn't grow too-big given the number of overloads of Write. 
private static Dictionary<Type, Action<BinaryWriter, object>> _lambdaCache = new Dictionary<Type, Action<BinaryWriter, object>>(); 

//... 

if (!_lambdaCache.ContainsKey(fi.FieldType)) 
{ 
    var binaryWriterParameter = Expression.Parameter(typeof(BinaryWriter)); 
    var valueParameter = Expression.Parameter(typeof(object)); 
    var call = Expression.Call(binaryWriterParameter, "Write", null, Expression.Convert(valueParameter, fi.FieldType)); 
    var lambda = Expression.Lambda<Action<BinaryWriter, object>>(call, binaryWriterParameter, valueParameter).Compile(); 
    _lambdaCache.Add(fi.FieldType, lambda); 
} 
var write = _lambdaCache[fi.FieldType]; 
write(bw, fi.GetValue(obj)); 

我們這裏做的是動態生成的代碼,以使您需要二進制作家呼叫。這聽起來比它更復雜,但我們正在做的是創建一個表達式到BinaryWriter的「寫」方法。我們還使用Expression.Convert動態地轉換它,所以調用Write的正確超載。我們需要BinaryWriter的兩個參數和寫入的值。最後,我們編譯lambda並將其緩存到該類型以供稍後重新使用。

根據您的需要,這將比使用BinaryWriter反射快得多。

+0

如果你添加一些關於如何以及爲什麼會這樣工作的更多細節,我會對此讚不絕口。 –

+0

這是否將「Write」的重載考慮在內?它叫'寫(Int32)已'當返回類型的'fi.GetValue(OBJ)''是Int32',儘管方法簽名'GetValue'是'對象的GetValue(對象)'。 – Polynomial

+0

除了它一個很好的解決方案通過字段值作爲常數,因此,你需要創建一個新的拉姆達並在每次序列化類/結構的任何實例時編譯。您可以添加一個Expression.Convert將字段值轉換爲正確的類型,然後使字段值爲參數?然後你可以重複地調用它,而不必再次編譯。 –

2

我爲protobuf-net做了一些非常相似的代碼; Type.GetTypeCode(...)來說是一個福音,允許switch

switch(Type.GetTypeCode(fi.FieldType)) { 
    case TypeCode.Int16: bw.Write((Int16)fi.GetValue(obj)); break 
    case TypeCode.UInt32: bw.Write((UInt16)fi.GetValue(obj)); break; 
     ... etc lots and lots 
} 

還是有點重複,但你只能在Type看一次 - 剩下的就是switch

如果使用的是4.0,另一個伎倆可能是:

dynamic value = fi.GetValue(obj); 
bw.Write(value); 

儘量挑選最合適的過載運行時。然而,在我看來,這是不足以在這裏使用dynamic足夠。

最後一個想法是: - 準備就在更爲複雜,但速度更快,並在執行時間(沒有任何這些檢查使用元編程(如ILGenerator)在運行時創建的代碼該模型)。

1

正確的版本我能想到的三個選項:

1)BinaryFormatter - 這可能是能夠完成你的任務很簡單,由Serialize方法。
2)如你所說,使用反射。代碼看起來像這樣:

// sample source data 
object src = (uint)234; 

var bwType = typeof(BinaryWriter); 
var argTypes = new Type[] { src.GetType() }; 
var m = bwType.GetMethod("Write", argTypes); 
var args = new object[] { src }; 
m.Invoke(bw, args); 

3)使用T4模板快速生成代碼。代碼仍然很難看,但至少需要很少的工作來維護。我在我的一些項目中經常使用這種模式,因爲它是兩個世界中最好的 - 沒有反射造成的性能損失,但是動態生成代碼的所有好處。

0

即使你沒有做其他事情,switch與字符串一起工作,它會使你更容易閱讀。

由於顯式的轉換工作:

Type t = Type.GetType(String.Concat("System.", fi.FieldType.Name)); 

然後使用

MethodInfo m = typeof(BinaryWriter).GetMethod("Write", new type[] { t }); 

如果它不是空

m.Invoke(bw, new object[] { fi.GetValue(obj) }); 

這是假設FieldType.Name對應於一個類型的範圍。沒有說出數組中的內容,但是如果它是Int16[],它只是一個小動作,可能是BinaryWriter的子類化,並且爲框中的某個類型不處理的類型添加了一些更多的重載。 如果你正在做很多這樣的事情,某種緩存Name,TypeMethodInfo可能會有用。