使用代码生成建立可扩展序列化器(上)
使用代碼生成建立可擴展序列化器(上)
地獄門神
?
在很多程序中,配置文件和用戶數據的保存和讀取都是一個需要考慮的問題。
在以前,用戶數據經常保存在INI文件中,后來出現了注冊表,于是也有保存在注冊表中的。
注冊表不是一種穩定的方式,不便于用戶管理。
INI和注冊表都難以保存復雜結構的對象,需要手寫很多代碼。
?
隨著XML和.Net的興起,越來越多的程序使用XML文件來保存用戶數據。
不過.Net的內置序列化器System.Xml.Serialization.XmlSerializer是在.Net 2.0之前推出的,對泛型的支持有限。
同時,由于其使用特性來標記數據模型中的類和變量,并需要對象實現IXmlSerializable。這是對數據模型的嚴重污染,非常不利于程序維護。
?
此外,出于性能的考慮,在數據達到一定規模之后,我們必須使用二進制序列化,以加快加載速度。
對于二進制序列化,有很多實現,而且一般都互不兼容。
由于很多二進制格式是已經事先確定的,要實現對這些二進制格式的反序列化和序列化支持,是非常困難的,沒有很好的工具解決這個問題,經常需要手工維護讀文件和寫文件兩部分代碼。
.Net的默認實現使得類必須實現ISerializable。這和XML序列化的問題是一樣的。
?
這兩種序列化器均難以方便的實現定制功能。
?
由于這些問題,我建立了兩個全新的可擴展序列化器,并使用相同的基礎結構。
設計目標是:
1)可擴展性,二進制序列化應可擴展支持任意二進制格式;
2)性能,不能太慢,對于多次序列化、反序列化,每次不應有較大的性能損失;
3)無污染,不應對模型數據進行任何插入式的標記;
4)可加的版本支持,這一部分應通過可擴展性來實現;
5)自動支持泛型集合,特別是List(T)和Dictionary(TKey, TValue)。
6)自動支持不變類型,即只讀的公開屬性和構造函數參數匹配的類型,例如KeyValuePair(TKey, TValue)。
?
.Net對于泛型的支持不夠完整,缺乏泛型λ表達式、泛型委托等語法元素,泛型約束也有很大的限制。
因此,代碼生成是唯一的強類型的選擇。另一個選擇是使用弱類型進行動態綁定,不過感覺性能和類型安全都不能保證,所以不用。
?
先上代碼。C#的代碼請參見附件。
?
數據模型如下:
?
PublicClassDataEntry
? ? Public Name AsString
? ? Public Data AsByte()
EndClass
?
PublicClassImmutableDataEntry(Of T)
? ? PublicReadOnlyProperty Name AsString
? ? ? ? Get
? ? ? ? ? ? Return NameValue
? ? ? ? EndGet
? ? EndProperty
? ? PublicReadOnlyProperty Data AsT
? ? ? ? Get
? ? ? ? ? ? Return DataValue
? ? ? ? EndGet
? ? EndProperty
?
? ? Private NameValue AsString
? ? Private DataValue AsT
? ? PublicSubNew(ByVal Name AsString, ByVal Data AsT)
? ? ? ? Me.NameValue = Name
? ? ? ? Me.DataValue = Data
? ? EndSub
EndClass
?
PublicClassDataObject
? ? Public DataEntries AsNewDictionary(OfString, DataEntry)
? ? Public ImmutableDataEntries AsNewDictionary(OfString, ImmutableDataEntry(OfByte()))
EndClass
?
XML序列化代碼如下:
?
'創建自定義XML序列化器實例
Dim mxs AsNew Firefly.Mapping.XmlSerializer
?
'創建數據
Dim Obj AsNewDataObject
Obj.DataEntries.Add("DataEntry1", NewDataEntryWith {.Name = "DataEntry1", .Data = NewByte() {1, 2, 3, 4, 5}})
Obj.DataEntries.Add("DataEntry2", NewDataEntryWith {.Name = "DataEntry2", .Data = NewByte() {6, 7, 8, 9, 10}})
Obj.ImmutableDataEntries.Add("ImmutableDataEntry1", NewImmutableDataEntry(OfByte())("ImmutableDataEntry1", NewByte() {1, 2, 3, 4, 5}))
Obj.ImmutableDataEntries.Add("ImmutableDataEntry2", NewImmutableDataEntry(OfByte())("ImmutableDataEntry2", NewByte() {6, 7, 8, 9, 10}))
?
'XML序列化
Dim Element = mxs.Write(Obj)
?
'XML反序列化
Dim RoundTripped = mxs.Read(OfDataObject)(Element)
?
'輸出到命令行
Dim Setting = NewXmlWriterSettingsWith {.Encoding = Console.Out.Encoding, .Indent = True, .OmitXmlDeclaration = False}
Using w = XmlWriter.Create(Console.Out, Setting)
? ? Element.Save(w)
EndUsing
?
這樣就自動生成以下XML文本:
?
<?xmlversion="1.0"encoding="gb2312"?>
<DataObject>
? <DataEntries>
? ? <KeyValuePairOfStringAndDataEntry>
? ? ? <Key>DataEntry1</Key>
? ? ? <Value>
? ? ? ? <Name>DataEntry1</Name>
? ? ? ? <Data>
? ? ? ? ? <Byte>1</Byte>
? ? ? ? ? <Byte>2</Byte>
? ? ? ? ? <Byte>3</Byte>
? ? ? ? ? <Byte>4</Byte>
? ? ? ? ? <Byte>5</Byte>
? ? ? ? </Data>
? ? ? </Value>
? ? </KeyValuePairOfStringAndDataEntry>
? ? <KeyValuePairOfStringAndDataEntry>
? ? ? <Key>DataEntry2</Key>
? ? ? <Value>
? ? ? ? <Name>DataEntry2</Name>
? ? ? ? <Data>
? ? ? ? ? <Byte>6</Byte>
? ? ? ? ? <Byte>7</Byte>
? ? ? ? ? <Byte>8</Byte>
? ? ? ? ? <Byte>9</Byte>
? ? ? ? ? <Byte>10</Byte>
? ? ? ? </Data>
? ? ? </Value>
? ? </KeyValuePairOfStringAndDataEntry>
? </DataEntries>
? <ImmutableDataEntries>
? ? <KeyValuePairOfStringAndImmutableDataEntryOfArrayOfByte>
? ? ? <Key>ImmutableDataEntry1</Key>
? ? ? <Value>
? ? ? ? <Name>ImmutableDataEntry1</Name>
? ? ? ? <Data>
? ? ? ? ? <Byte>1</Byte>
? ? ? ? ? <Byte>2</Byte>
? ? ? ? ? <Byte>3</Byte>
? ? ? ? ? <Byte>4</Byte>
? ? ? ? ? <Byte>5</Byte>
? ? ? ? </Data>
? ? ? </Value>
? ? </KeyValuePairOfStringAndImmutableDataEntryOfArrayOfByte>
? ? <KeyValuePairOfStringAndImmutableDataEntryOfArrayOfByte>
? ? ? <Key>ImmutableDataEntry2</Key>
? ? ? <Value>
? ? ? ? <Name>ImmutableDataEntry2</Name>
? ? ? ? <Data>
? ? ? ? ? <Byte>6</Byte>
? ? ? ? ? <Byte>7</Byte>
? ? ? ? ? <Byte>8</Byte>
? ? ? ? ? <Byte>9</Byte>
? ? ? ? ? <Byte>10</Byte>
? ? ? ? </Data>
? ? ? </Value>
? ? </KeyValuePairOfStringAndImmutableDataEntryOfArrayOfByte>
? </ImmutableDataEntries>
</DataObject>
?
這個文件中,字節數組使用了默認的集合表示,不利于查看和修改,我們可以使用擴展機制來處理這個問題。
兩個序列化器均提供三種擴展機制:
1) PutReader|PutWriter,用于提供直接的讀寫替代,直接操作需要讀寫的對象和數據流|數據樹;
2) PutReaderTranslator|PutWriterTranslator,提供更高層的抽象,用于將需要讀寫的對象替代成另一種對象,交給序列化器做后續處理;
3) (ReaderResolver|WriterResolver).(ProjectorResolvers|AggregatorResolvers),提供直接的類型解析替代,但此機制中類型均為運行時類型,編寫代碼較麻煩。
詳細的說明將在后面介紹,這里我們使用2),即對象替代。
?
聲明對象替代器:
?
'用于將字節數組轉換為字符串處理
PrivateClassByteArrayCodec
? ? ImplementsIProjectorToProjectorRangeTranslator(OfByte(), String) 'Reader
? ? ImplementsIProjectorToProjectorDomainTranslator(OfByte(), String) 'Writer
?
? ? PublicFunction TranslateProjectorToProjectorRange(Of D)(ByVal Projector AsFunc(OfD, String)) AsFunc(OfD, Byte()) ImplementsIProjectorToProjectorRangeTranslator(OfByte(), String).TranslateProjectorToProjectorRange
? ? ? ? ReturnFunction(k) Regex.Split(Projector(k).Trim(" \t\r\n".Descape.ToCharArray), "( |\t|\r|\n)+", RegexOptions.ExplicitCapture).Select(Function(s) Byte.Parse(s, Globalization.NumberStyles.HexNumber)).ToArray
? ? EndFunction
? ? PublicFunction TranslateProjectorToProjectorDomain(Of R)(ByVal Projector AsFunc(OfString, R)) AsFunc(OfByte(), R) ImplementsIProjectorToProjectorDomainTranslator(OfByte(), String).TranslateProjectorToProjectorDomain
? ? ? ? ReturnFunction(ba) Projector(String.Join(" ", (ba.Select(Function(b) b.ToString("X2")).ToArray)))
? ? EndFunction
EndClass
?
將對象替代器注冊到序列化器:
?
Dim mxs AsNew Firefly.Mapping.XmlSerializer
mxs.PutReaderTranslator(NewByteArrayCodec)
mxs.PutWriterTranslator(NewByteArrayCodec)
?
這樣就自動生成以下XML文本:
?
<?xmlversion="1.0"encoding="gb2312"?>
<DataObject>
? <DataEntries>
? ? <KeyValuePairOfStringAndDataEntry>
? ? ? <Key>DataEntry1</Key>
? ? ? <Value>
? ? ? ? <Name>DataEntry1</Name>
? ? ? ? <Data>01 02 03 04 05</Data>
? ? ? </Value>
? ? </KeyValuePairOfStringAndDataEntry>
? ? <KeyValuePairOfStringAndDataEntry>
? ? ? <Key>DataEntry2</Key>
? ? ? <Value>
? ? ? ? <Name>DataEntry2</Name>
? ? ? ? <Data>06 07 08 09 0A</Data>
? ? ? </Value>
? ? </KeyValuePairOfStringAndDataEntry>
? </DataEntries>
? <ImmutableDataEntries>
? ? <KeyValuePairOfStringAndImmutableDataEntryOfArrayOfByte>
? ? ? <Key>ImmutableDataEntry1</Key>
? ? ? <Value>
? ? ? ? <Name>ImmutableDataEntry1</Name>
? ? ? ? <Data>01 02 03 04 05</Data>
? ? ? </Value>
? ? </KeyValuePairOfStringAndImmutableDataEntryOfArrayOfByte>
? ? <KeyValuePairOfStringAndImmutableDataEntryOfArrayOfByte>
? ? ? <Key>ImmutableDataEntry2</Key>
? ? ? <Value>
? ? ? ? <Name>ImmutableDataEntry2</Name>
? ? ? ? <Data>06 07 08 09 0A</Data>
? ? ? </Value>
? ? </KeyValuePairOfStringAndImmutableDataEntryOfArrayOfByte>
? </ImmutableDataEntries>
</DataObject>
?
這里解釋一下ByteArrayCodec的作用。
ByteArrayCodec是一個轉換字節數組到字符串,并轉換回來的類,實現了兩個接口:
?
PublicInterfaceIProjectorToProjectorRangeTranslator(Of R, M)
? ? Function TranslateProjectorToProjectorRange(Of D)(ByVal Projector As Func(Of D, M)) As Func(Of D, R)
EndInterface
?
PublicInterfaceIProjectorToProjectorDomainTranslator(Of D, M)
? ? Function TranslateProjectorToProjectorDomain(Of R)(ByVal Projector As Func(OfM, R)) As Func(OfD, R)
EndInterface
?
這兩個接口,其實是用來約束兩個泛型高階函數。D是輸入類型,M是中間類型,R是輸出類型。
所謂的Projector,是指投影函數,即一個將原來的對象轉換為具有相同信息或者更少信息的對象的函數。
與其相對的,我還定義了一種叫Aggregator的東西,即聚合函數,用于將一個對象的信息加入到另一個已有對象。這個現在暫不描述。
通常我們認為Projector比Aggregator更好書寫。
IProjectorToProjectorRangeTranslator,就是用來將一個Projector的值域類型變換到另一個類型,也就是:
IProjectorToProjectorRangeTranslator(D, M, R): Projector(D, M) -> Projector(D, R)
由于.Net不支持返回泛型λ表達式,不能做泛型參數偏特化,因此將本來的三個泛型參數(D, M, R)分成兩組(R, M)和(D),(R, M)定義為接口參數,(D)定義成函數類型參數。
這樣我們可以先對(R, M)進行特化,再對(D)進行特化,使得高階函數的實現與定義域類型D無關。
同樣,IProjectorToProjectorDomainTranslator用于將一個Projector的定義域類型變換到另一個類型。
?
在ByteArrayCodec中:
TranslateProjectorToProjectorRange用于將Projector(D, String)轉化為Projector(D, Byte()),內部做了String到Byte()的轉換,使用正則表達式實現;
TranslateProjectorToProjectorDomain用于將Projector(Byte(), R)轉化為Projector(String, R),內部做了Byte()到String的轉換,使用Byte.ToString("X2")來完成。
?
下一步,我們需要變更數據模型,但需要保持對已有用戶數據的兼容。
這個時候,我們仍然通過對象替代來解決。
?
首先變更數據模型,將DataEntry增加一個Attribute字段,原DataEntry更名保留:
?
'版本1的DataEntry
PublicClassDataEntryVersion1
? ? Public Name AsString
? ? Public Data AsByte()
EndClass
?
'當前版本(版本2)的DataEntry
PublicClassDataEntry
? ? Public Name AsString
? ? Public Data AsByte()
? ? Public Attribute AsString
EndClass
?
增加一個對象替代器:
'用于適配DataEntry的版本1和版本2
PublicClassDataEntryVersion1To2Translator
? ? ImplementsIProjectorToProjectorRangeTranslator(OfDataEntry, DataEntryVersion1) 'Reader
? ? ImplementsIProjectorToProjectorDomainTranslator(OfDataEntry, DataEntryVersion1) 'Writer
?
? ? PublicFunction TranslateProjectorToProjectorRange(Of D)(ByVal Projector AsFunc(OfD, DataEntryVersion1)) AsFunc(OfD, DataEntry) ImplementsIProjectorToProjectorRangeTranslator(OfDataEntry, DataEntryVersion1).TranslateProjectorToProjectorRange
? ? ? ? ReturnFunction(DomainValue)
? ? ? ? ? ? ? ? ? ?Dim v1 = Projector(DomainValue)
? ? ? ? ? ? ? ? ? ?ReturnNewDataEntryWith {.Name = v1.Name, .Data = v1.Data, .Attribute = "Version1's attribute"}
? ? ? ? ? ? ? ?EndFunction
? ? EndFunction
? ? PublicFunction TranslateProjectorToProjectorDomain(Of R)(ByVal Projector AsFunc(OfDataEntryVersion1, R)) AsFunc(OfDataEntry, R) ImplementsIProjectorToProjectorDomainTranslator(OfDataEntry, DataEntryVersion1).TranslateProjectorToProjectorDomain
? ? ? ? ReturnFunction(v2)
? ? ? ? ? ? ? ? ? ?Dim v1 = NewDataEntryVersion1With {.Name = v2.Name, .Data = v2.Data}
? ? ? ? ? ? ? ? ? ?Return Projector(v1)
? ? ? ? ? ? ? ?EndFunction
? ? EndFunction
EndClass
?
然后聲明兩個版本的序列化器,版本1不放入DataEntryVersion1To2Translator,版本2放入DataEntryVersion1To2Translator。
為了在文檔中加入版本標志,我們可以使用XML元素的Attribute,在寫入后增加標記,如:
?
Dim Element = SerializerVersion2.Write(Obj)
Element.@<SchemaType> = "MyDataFormat"
Element.@<Version> = 2
?
在讀取的時候,首先讀取XElement:
?
Dim SchemaType = Element.@<SchemaType>
If SchemaType <> "MyDataFormat"ThenThrowNewInvalidDataException("數據不是MYDF格式數據")
Dim Version = Integer.Parse(Element.@<Version>)
?
再通過版本號來選擇序列化器進行反序列化。
?
<?xmlversion="1.0"encoding="gb2312"?>
<DataObjectSchemaType="MyDataFormat"Version="2">
?
這樣生成的第二個版本的XML文件就會具有版本號,同時程序可以對各個版本的文件進行兼容。
不會出現手寫代碼時,由于要兼容,出現多套數據模型或者難以修改數據模型的問題。
詳細的代碼,以及二進制序列化的示例請參見附件,有VB和C#兩個版本的示例代碼,和一個簡化的庫。
簡化的庫是因為這兩個序列化器均是作為螢火蟲漢化框架的一部分來開發的。
?
關于內部實現,主要是通過System.Linq.Expression來進行的,生成的表達式可以被垃圾回收。
實現我將在下篇中描述。
?
所有的示例代碼均按Public Domain授權,所有的庫代碼均按BSD協議授權。如果需要在GPL程序中使用,請與我單獨聯系授權。
?
最后攜帶一點私貨,為了解耦我們不要拘泥于面向對象。
?
示例下載地址:
http://files.cnblogs.com/Rex/FireflyForSerializing.rar
?
轉載于:https://www.cnblogs.com/Rex/archive/2010/11/18/1880902.html
總結
以上是生活随笔為你收集整理的使用代码生成建立可扩展序列化器(上)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机中的颜色XIII——颜色转换的快速
- 下一篇: BAT教程 第三节(FOR命令中的变量)