Mar 21

ObjectReader, v2.0

呃。。。这次支持了数组和内嵌其它类型,另外发现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

        

Mar 13

在.Net Framework 2.0中使用扩展方法

因为扩展方法需要.net 3.5的缘故,装好VS2008之后一直没用过。刚才偶然在google发现用一个小技巧就能在.net 2.0中使用。

很简单,新建一个类,内容如下:

      
Namespace System.Runtime.CompilerServices
    <AttributeUsage(AttributeTargets.Class Or AttributeTargets.Method Or AttributeTargets.Assembly)> _
    Public Class ExtensionAttribute
        Inherits Attribute
    End Class
End Namespace

        

注意要在项目属性里把根命名空间设置为空。然后就照常定义扩展方法就可以了(C#的this关键字没测试过,不知道有没有问题)。VS2008会把这些方法正确识别为扩展方法。