使用代码生成建立可扩展序列化器(上)
使用代碼生成建立可擴(kuò)展序列化器(上)
地獄門神
?
在很多程序中,配置文件和用戶數(shù)據(jù)的保存和讀取都是一個需要考慮的問題。
在以前,用戶數(shù)據(jù)經(jīng)常保存在INI文件中,后來出現(xiàn)了注冊表,于是也有保存在注冊表中的。
注冊表不是一種穩(wěn)定的方式,不便于用戶管理。
INI和注冊表都難以保存復(fù)雜結(jié)構(gòu)的對象,需要手寫很多代碼。
?
隨著XML和.Net的興起,越來越多的程序使用XML文件來保存用戶數(shù)據(jù)。
不過.Net的內(nèi)置序列化器System.Xml.Serialization.XmlSerializer是在.Net 2.0之前推出的,對泛型的支持有限。
同時,由于其使用特性來標(biāo)記數(shù)據(jù)模型中的類和變量,并需要對象實(shí)現(xiàn)IXmlSerializable。這是對數(shù)據(jù)模型的嚴(yán)重污染,非常不利于程序維護(hù)。
?
此外,出于性能的考慮,在數(shù)據(jù)達(dá)到一定規(guī)模之后,我們必須使用二進(jìn)制序列化,以加快加載速度。
對于二進(jìn)制序列化,有很多實(shí)現(xiàn),而且一般都互不兼容。
由于很多二進(jìn)制格式是已經(jīng)事先確定的,要實(shí)現(xiàn)對這些二進(jìn)制格式的反序列化和序列化支持,是非常困難的,沒有很好的工具解決這個問題,經(jīng)常需要手工維護(hù)讀文件和寫文件兩部分代碼。
.Net的默認(rèn)實(shí)現(xiàn)使得類必須實(shí)現(xiàn)ISerializable。這和XML序列化的問題是一樣的。
?
這兩種序列化器均難以方便的實(shí)現(xiàn)定制功能。
?
由于這些問題,我建立了兩個全新的可擴(kuò)展序列化器,并使用相同的基礎(chǔ)結(jié)構(gòu)。
設(shè)計目標(biāo)是:
1)可擴(kuò)展性,二進(jìn)制序列化應(yīng)可擴(kuò)展支持任意二進(jìn)制格式;
2)性能,不能太慢,對于多次序列化、反序列化,每次不應(yīng)有較大的性能損失;
3)無污染,不應(yīng)對模型數(shù)據(jù)進(jìn)行任何插入式的標(biāo)記;
4)可加的版本支持,這一部分應(yīng)通過可擴(kuò)展性來實(shí)現(xiàn);
5)自動支持泛型集合,特別是List(T)和Dictionary(TKey, TValue)。
6)自動支持不變類型,即只讀的公開屬性和構(gòu)造函數(shù)參數(shù)匹配的類型,例如KeyValuePair(TKey, TValue)。
?
.Net對于泛型的支持不夠完整,缺乏泛型λ表達(dá)式、泛型委托等語法元素,泛型約束也有很大的限制。
因此,代碼生成是唯一的強(qiáng)類型的選擇。另一個選擇是使用弱類型進(jìn)行動態(tài)綁定,不過感覺性能和類型安全都不能保證,所以不用。
?
先上代碼。C#的代碼請參見附件。
?
數(shù)據(jù)模型如下:
?
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序列化代碼如下:
?
'創(chuàng)建自定義XML序列化器實(shí)例
Dim mxs AsNew Firefly.Mapping.XmlSerializer
?
'創(chuàng)建數(shù)據(jù)
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>
?
這個文件中,字節(jié)數(shù)組使用了默認(rèn)的集合表示,不利于查看和修改,我們可以使用擴(kuò)展機(jī)制來處理這個問題。
兩個序列化器均提供三種擴(kuò)展機(jī)制:
1) PutReader|PutWriter,用于提供直接的讀寫替代,直接操作需要讀寫的對象和數(shù)據(jù)流|數(shù)據(jù)樹;
2) PutReaderTranslator|PutWriterTranslator,提供更高層的抽象,用于將需要讀寫的對象替代成另一種對象,交給序列化器做后續(xù)處理;
3) (ReaderResolver|WriterResolver).(ProjectorResolvers|AggregatorResolvers),提供直接的類型解析替代,但此機(jī)制中類型均為運(yùn)行時類型,編寫代碼較麻煩。
詳細(xì)的說明將在后面介紹,這里我們使用2),即對象替代。
?
聲明對象替代器:
?
'用于將字節(jié)數(shù)組轉(zhuǎn)換為字符串處理
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是一個轉(zhuǎn)換字節(jié)數(shù)組到字符串,并轉(zhuǎn)換回來的類,實(shí)現(xiàn)了兩個接口:
?
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
?
這兩個接口,其實(shí)是用來約束兩個泛型高階函數(shù)。D是輸入類型,M是中間類型,R是輸出類型。
所謂的Projector,是指投影函數(shù),即一個將原來的對象轉(zhuǎn)換為具有相同信息或者更少信息的對象的函數(shù)。
與其相對的,我還定義了一種叫Aggregator的東西,即聚合函數(shù),用于將一個對象的信息加入到另一個已有對象。這個現(xiàn)在暫不描述。
通常我們認(rèn)為Projector比Aggregator更好書寫。
IProjectorToProjectorRangeTranslator,就是用來將一個Projector的值域類型變換到另一個類型,也就是:
IProjectorToProjectorRangeTranslator(D, M, R): Projector(D, M) -> Projector(D, R)
由于.Net不支持返回泛型λ表達(dá)式,不能做泛型參數(shù)偏特化,因此將本來的三個泛型參數(shù)(D, M, R)分成兩組(R, M)和(D),(R, M)定義為接口參數(shù),(D)定義成函數(shù)類型參數(shù)。
這樣我們可以先對(R, M)進(jìn)行特化,再對(D)進(jìn)行特化,使得高階函數(shù)的實(shí)現(xiàn)與定義域類型D無關(guān)。
同樣,IProjectorToProjectorDomainTranslator用于將一個Projector的定義域類型變換到另一個類型。
?
在ByteArrayCodec中:
TranslateProjectorToProjectorRange用于將Projector(D, String)轉(zhuǎn)化為Projector(D, Byte()),內(nèi)部做了String到Byte()的轉(zhuǎn)換,使用正則表達(dá)式實(shí)現(xiàn);
TranslateProjectorToProjectorDomain用于將Projector(Byte(), R)轉(zhuǎn)化為Projector(String, R),內(nèi)部做了Byte()到String的轉(zhuǎn)換,使用Byte.ToString("X2")來完成。
?
下一步,我們需要變更數(shù)據(jù)模型,但需要保持對已有用戶數(shù)據(jù)的兼容。
這個時候,我們?nèi)匀煌ㄟ^對象替代來解決。
?
首先變更數(shù)據(jù)模型,將DataEntry增加一個Attribute字段,原DataEntry更名保留:
?
'版本1的DataEntry
PublicClassDataEntryVersion1
? ? Public Name AsString
? ? Public Data AsByte()
EndClass
?
'當(dāng)前版本(版本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。
為了在文檔中加入版本標(biāo)志,我們可以使用XML元素的Attribute,在寫入后增加標(biāo)記,如:
?
Dim Element = SerializerVersion2.Write(Obj)
Element.@<SchemaType> = "MyDataFormat"
Element.@<Version> = 2
?
在讀取的時候,首先讀取XElement:
?
Dim SchemaType = Element.@<SchemaType>
If SchemaType <> "MyDataFormat"ThenThrowNewInvalidDataException("數(shù)據(jù)不是MYDF格式數(shù)據(jù)")
Dim Version = Integer.Parse(Element.@<Version>)
?
再通過版本號來選擇序列化器進(jìn)行反序列化。
?
<?xmlversion="1.0"encoding="gb2312"?>
<DataObjectSchemaType="MyDataFormat"Version="2">
?
這樣生成的第二個版本的XML文件就會具有版本號,同時程序可以對各個版本的文件進(jìn)行兼容。
不會出現(xiàn)手寫代碼時,由于要兼容,出現(xiàn)多套數(shù)據(jù)模型或者難以修改數(shù)據(jù)模型的問題。
詳細(xì)的代碼,以及二進(jìn)制序列化的示例請參見附件,有VB和C#兩個版本的示例代碼,和一個簡化的庫。
簡化的庫是因為這兩個序列化器均是作為螢火蟲漢化框架的一部分來開發(fā)的。
?
關(guān)于內(nèi)部實(shí)現(xiàn),主要是通過System.Linq.Expression來進(jìn)行的,生成的表達(dá)式可以被垃圾回收。
實(shí)現(xiàn)我將在下篇中描述。
?
所有的示例代碼均按Public Domain授權(quán),所有的庫代碼均按BSD協(xié)議授權(quán)。如果需要在GPL程序中使用,請與我單獨(dú)聯(lián)系授權(quán)。
?
最后攜帶一點(diǎn)私貨,為了解耦我們不要拘泥于面向?qū)ο蟆?/p>
?
示例下載地址:
http://files.cnblogs.com/Rex/FireflyForSerializing.rar
?
轉(zhuǎn)載于:https://www.cnblogs.com/Rex/archive/2010/11/18/1880902.html
總結(jié)
以上是生活随笔為你收集整理的使用代码生成建立可扩展序列化器(上)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机中的颜色XIII——颜色转换的快速
- 下一篇: BAT教程 第三节(FOR命令中的变量)