呃。。。这次支持了数组和内嵌其它类型,另外发现Field和Property混用的话顺序会出错,所以加上了限制。
Imports System.Reflection
Imports System.IO
Imports System.Runtime.Serialization
Namespace SAPStudio.Utils.IO
<AttributeUsage(AttributeTargets.Field Or AttributeTargets.Property, AllowMultiple:=False)> _
Public Class ObjectReaderWriterIncludeAttribute
Inherits Attribute
End Class
<AttributeUsage(AttributeTargets.Field Or AttributeTargets.Property, AllowMultiple:=False)> _
Public Class ArraySizeAttribute
Inherits Attribute
Private _size As Integer
Public ReadOnly Property Size() As Integer
Get
Return _size
End Get
End Property
Public Sub New(ByVal size As Integer)
_size = size
End Sub
End Class
<AttributeUsage(AttributeTargets.Class Or AttributeTargets.Struct, AllowMultiple:=False)> _
Public Class ObjectReaderWriterOptionsAttribute
Inherits Attribute
Private _dataMember As DataMemberTypes
Public ReadOnly Property DataMember() As DataMemberTypes
Get
Return _dataMember
End Get
End Property
Sub New(ByVal dataMember As DataMemberTypes)
_dataMember = dataMember
End Sub
End Class
Public Enum DataMemberTypes
Field
[Property]
End Enum
Friend Structure Getter
Public Method As MethodInfo
Public InvokeWithAdditionalData As Boolean
Public AdditionalData As Object()
Public Function Invoke(ByVal reader As BinaryReader) As Object
Return Method.Invoke(reader, If(InvokeWithAdditionalData, New Object() {reader, AdditionalData}, Nothing))
End Function
End Structure
''' <summary>
''' Construct a object using data from a <see cref="System.IO.BinaryReader" /> or <see cref="System.IO.Stream" />.
''' </summary>
''' <typeparam name="T">Type of the object. Can be a ValueType</typeparam>
''' <remarks>Because Type.GetMembers only preserves declaration order of members with same type, You can only use either property or field to store data. Tested in .Net Framework 3.0, applies to .Net 2.0 too(I think). Apply ObjectReaderWriterOptions attribute to the type definition to choose which to use.</remarks>
Public NotInheritable Class ObjectReader(Of T As New)
Shared _gettersAndSetters As New List(Of KeyValuePair(Of Getter, MemberInfo))
Shared Sub New()
Dim options = CType(Attribute.GetCustomAttribute(GetType(T), GetType(ObjectReaderWriterOptionsAttribute)), ObjectReaderWriterOptionsAttribute)
Dim dataMemberType As MemberTypes = If(options Is Nothing OrElse options.DataMember = DataMemberTypes.Field, MemberTypes.Field, MemberTypes.Property)
For Each m In GetType(T).GetMembers(BindingFlags.Public Or BindingFlags.Instance)
If m.MemberType <> dataMemberType Then Continue For
Dim fieldType As Type
If m.MemberType = MemberTypes.Field Then
Dim fi = CType(m, FieldInfo)
If fi.IsNotSerialized OrElse fi.IsInitOnly OrElse fi.IsLiteral Then Continue For
fieldType = fi.FieldType
Else
Dim pi = CType(m, PropertyInfo)
If Not (pi.CanWrite AndAlso pi.CanRead) Then Continue For
fieldType = pi.PropertyType
End If
Dim getter As Getter
If fieldType.IsPrimitive Then
getter = GetGetterPrimitive(fieldType)
ElseIf fieldType.IsArray AndAlso Attribute.IsDefined(m, GetType(ArraySizeAttribute)) Then
Dim elementType = fieldType.GetElementType()
If elementType.IsPrimitive OrElse Attribute.IsDefined(m, GetType(ObjectReaderWriterIncludeAttribute)) Then
getter = GetGetterArray(elementType, CType(Attribute.GetCustomAttribute(m, GetType(ArraySizeAttribute)), ArraySizeAttribute).Size)
End If
ElseIf Attribute.IsDefined(m, GetType(ObjectReaderWriterIncludeAttribute)) Then
getter = GetGetterNested(fieldType)
Else
Continue For
End If
If getter.Method Is Nothing Then Continue For
_gettersAndSetters.Add(New KeyValuePair(Of Getter, MemberInfo)(getter, m))
Next
End Sub
Public Shared Function Read(ByVal br As BinaryReader) As T
If _gettersAndSetters.Count = 0 Then Throw New NotSupportedException("No member in specified type is supported")
If GetType(T).IsValueType Then Return ReadValueType(br) 'VB will preserve value of ValueType when calling a method if the type of the variable is Object
Dim ret As New T
For Each pair In _gettersAndSetters
Dim value = pair.Key.Invoke(br)
If pair.Value.MemberType = MemberTypes.Field Then
CType(pair.Value, FieldInfo).SetValue(ret, value)
Else
CType(pair.Value, PropertyInfo).SetValue(ret, value, Nothing)
End If
Next
Return ret
End Function
Public Shared Function Read(ByVal s As Stream, ByVal isLittleEndian As Boolean) As T
Using wrapper As New StreamWrapper(s), br = If(isLittleEndian, New BinaryReader(wrapper), New BigEndianBinaryReader(wrapper))
Return Read(br)
End Using
End Function
Public Shared Function Read(ByVal s As Stream) As T
Return Read(s, True)
End Function
Private Shared Function ReadValueType(ByVal br As BinaryReader) As T
Dim ret As ValueType = CType(Activator.CreateInstance(GetType(T)), ValueType)
For Each pair In _gettersAndSetters
Dim value = pair.Key.Invoke(br)
If pair.Value.MemberType = MemberTypes.Field Then
CType(pair.Value, FieldInfo).SetValue(ret, value)
Else
CType(pair.Value, PropertyInfo).SetValue(ret, value, Nothing)
End If
Next
Return CType(DirectCast(ret, Object), T)
End Function
Private Shared Function GetGetterPrimitive(ByVal type As Type) As Getter
Dim brType = GetType(BinaryReader)
Dim ret = New Getter With {.Method = brType.GetMethod("Read" & type.Name, BindingFlags.Instance Or BindingFlags.Public, Nothing, CallingConventions.Any, New Type() {}, Nothing), .InvokeWithAdditionalData = False}
Return ret
End Function
Private Shared Function GetGetterNested(ByVal type As Type) As Getter
Dim orType = GetType(ObjectReader(Of )).MakeGenericType(type)
Try
Return New Getter With { _
.Method = orType.GetMethod("ReadNested", BindingFlags.Static Or BindingFlags.NonPublic, Nothing, CallingConventions.Any, New Type() {GetType(BinaryReader), GetType(Object())}, Nothing), _
.InvokeWithAdditionalData = True}
Catch ex As Exception
Return Nothing
End Try
End Function
Private Shared Function ReadNested(ByVal reader As BinaryReader, ByVal dummy As Object()) As T
Return Read(reader)
End Function
Private Shared Function GetGetterArray(ByVal type As Type, ByVal count As Integer) As Getter
Dim elementGetter As Getter
If type.IsPrimitive Then
elementGetter = GetGetterPrimitive(type)
Else
elementGetter = GetGetterNested(type)
End If
Try
Return New Getter With { _
.Method = GetType(ObjectReader(Of T)).GetMethod("ReadArray", BindingFlags.Static Or BindingFlags.NonPublic, Nothing, CallingConventions.Any, New Type() {GetType(BinaryReader), GetType(Object())}, Nothing).MakeGenericMethod(type), _
.InvokeWithAdditionalData = True, _
.AdditionalData = New Object() {elementGetter, count}}
Catch ex As Exception
Return Nothing
End Try
End Function
Private Shared Function ReadArray(Of U As New)(ByVal reader As BinaryReader, ByVal data() As Object) As U() 'data: element getter, array size
Dim arraySize As Integer = CType(data(1), Integer)
If GetType(U) Is GetType(Byte) Then 'Optimized method
Dim ret = reader.ReadBytes(arraySize)
If ret.Length < arraySize Then
Throw New EndOfStreamException()
End If
Return CType(CType(ret, Object), U())
Else
Dim elementGetter As Getter = CType(data(0), Getter)
Dim ret As New List(Of U)
For i = 0 To arraySize - 1
ret.Add(CType(elementGetter.Invoke(reader), U))
Next
Return ret.ToArray()
End If
End Function
Private Sub New()
End Sub
End Class
End Namespace