我想根据您的回答提出一个面向对象的解决方案,按照您的要求使用 Visual Basic。
免责声明:
请记住,我不是 VB.NET 开发人员。我提供的代码已经过测试和运行,但肯定需要一些特定于语言的改进。
我假设您正在使用Test 类的实例,因为它公开的字段不是Shared
主要思想
分析您的需求,我发现这很重要:
- 将字段的命名策略集中在一个地方,以使您的解决方案具有可维护性
- 注意字段的排序。如果您有 Prefix_1、Prefix_2 和 Prefix_11,那么您可能会以 错误的方式排序:Prefix_1、Prefix_11 和 Prefix_2。
- 验证没有丢失的字段名称(即从 Prefix_1 跳转到 Prefix_3)
根据您的要求,我在名为 StringChunkField 的类中对每个保存字符串块的字段进行了建模。
该类对每个包含字符串块的前缀字段进行建模,并具有以下职责:
- 提供有关字段本身的信息:名称、编号、包含的字符串块以及其中的字符数
- 集中有关用于命名字段的格式和编号的信息。这里定义了要查找的前缀,以字段名称开头的数字。
- 从上一条可以判断字段是否以字符串开头,字段是否为StringChunkField。
-
实现 IComparable 将排序逻辑集中在一个地方(它基于字段编号)
Imports System.Reflection
Friend Class StringChunkField
Implements IComparable(Of StringChunkField)
#Region "Fields"
Private ReadOnly _number As Integer
Private _name As String
Private _stringChunk As String
Private Shared _beginningOfStringFieldNumber As Integer = 1
Private Shared _namePrefix As String = "Prefix_"
#End Region
Public Sub New(ByRef field As FieldInfo, ByRef target As Object)
_name = field.Name
_stringChunk = field.GetValue(target)
_number = ExtractFieldNumber(field.Name)
End Sub
#Region "Properties"
' Returns the field's number
Public ReadOnly Property Number() As Integer
Get
Return _number
End Get
End Property
' Returns the field's name (includes the number also)
Public ReadOnly Property Name() As String
Get
Return _name
End Get
End Property
' Returns the chunk of the string this fields holds
Public ReadOnly Property StringChunk() As String
Get
Return _stringChunk
End Get
End Property
' Returns the number of characters held in this field
Public ReadOnly Property NumberOfCharacters() As Integer
Get
If (String.IsNullOrEmpty(StringChunk)) Then
Return 0
Else
Return StringChunk.Length
End If
End Get
End Property
Public Shared ReadOnly Property BeginningOfStringFieldNumber() As String
Get
Return _beginningOfStringFieldNumber
End Get
End Property
#End Region
#Region "Comparison"
Public Function CompareTo(ByVal other As StringChunkField) As Integer Implements IComparable(Of StringChunkField).CompareTo
Return Number.CompareTo(other.Number)
End Function
Function IsFollowedBy(ByVal other As StringChunkField) As Object
Return other.Number = Number + 1
End Function
#End Region
#Region "Testing"
Public Function HoldsBeginingOfTheString() As Boolean
Return Number = 1
End Function
Public Shared Function IsPrefixField(ByVal field As FieldInfo) As Boolean
Return field.Name.StartsWith(_namePrefix)
End Function
#End Region
Private Function ExtractFieldNumber(ByVal fieldName As String) As Integer
Dim fieldNumber As String = fieldName.Replace(_namePrefix, String.Empty)
Return Integer.Parse(fieldNumber)
End Function
End Class
现在我们已经定义了什么是StringChunkField,使用什么名称前缀以及如何构建它,我们可以使用TypeEmbeddedStringReader 类的实例查询一个对象以获取它包含的字符串。
它的职责是:
- 查找对象中的所有
StringChunkFields 呈现
- 验证找到的字段编号是否根据
StringChunkField 中定义的基数开始,以及编号是否连续
-
从StringChunkFields 值重建对象中嵌入的字符串
Imports System.Reflection
Imports System.Text
Public Class TypeEmbeddedStringReader
Public Shared Function ReadStringFrom(ByRef target As Object) As String
' Get all prefix fields from target
' Each StringChunkField hold a chunk of the String to rebuild
Dim prefixFields As IEnumerable(Of StringChunkField) = GetPrefixFieldsFrom(target)
' There must be, at least, one StringChunkField
ValidateFieldsFound(prefixFields)
' The first StringChunkField must hold the beggining of the string (be numbered as one)
ValidateFieldNumbersBeginAtOne(prefixFields)
' Ensure that no StringChunkField number were skipped
ValidateFieldNumbersAreConsecutive(prefixFields)
' Calculate the total number of chars of the string to rebuild to initialize StringBuilder and make it more efficient
Dim totalChars As Integer = CalculateTotalNumberOfCharsIn(prefixFields)
Dim result As StringBuilder = New StringBuilder(totalChars)
' Rebuild the string
For Each field In prefixFields
result.Append(field.StringChunk)
Next
' We're done
Return result.ToString()
End Function
#Region "Validation"
Private Shared Sub ValidateFieldsFound(ByVal fields As List(Of StringChunkField))
If (fields.Count = 0) Then Throw New ArgumentException("Does not contains any StringChunkField", "target")
End Sub
Private Shared Sub ValidateFieldNumbersBeginAtOne(ByVal fields As List(Of StringChunkField))
' Get the first StringChunkField found
Dim firstStringChunkField As StringChunkField = fields.First
' If does not holds the begining of the string...
If (firstStringChunkField.HoldsBeginingOfTheString() = False) Then
' Throw an exception with a meaningful error message
Dim invalidFirstPrefixField = String.Format("The first StringChunkField found, '{0}', does not holds the beggining of the string. If holds the beggining of the string, it should be numbered as '{1}'.", firstStringChunkField.Name, StringChunkField.BeginningOfStringFieldNumber)
Throw New ArgumentException(invalidFirstPrefixField, "target")
End If
End Sub
Private Shared Sub ValidateFieldNumbersAreConsecutive(ByVal fields As List(Of StringChunkField))
For index = 0 To fields.Count - 2
' Get the current and next field in fields
Dim currentField As StringChunkField = fields(index)
Dim nextField As StringChunkField = fields(index + 1)
' If the numbers are consecutive, continue checking
If (currentField.IsFollowedBy(nextField)) Then Continue For
' If not, throw an exception with a meaningful error message
Dim missingFieldMessage As String = String.Format("At least one StringChunkField between '{0}' and '{1}' is missing", currentField.Name, nextField.Name)
Throw New ArgumentException(missingFieldMessage, "target")
Next
End Sub
#End Region
Private Shared Function CalculateTotalNumberOfCharsIn(ByVal fields As IEnumerable(Of StringChunkField)) As Integer
Return fields.Sum(Function(field) field.NumberOfCharacters)
End Function
Private Shared Function GetPrefixFieldsFrom(ByVal target As Object) As List(Of StringChunkField)
' Find all fields int the target object
Dim fields As FieldInfo() = target.GetType().GetFields()
' Select the ones that are PrefixFields
Dim prefixFields As IEnumerable(Of StringChunkField) = From field In fields Where StringChunkField.IsPrefixField(field) Select New StringChunkField(field, target)
' Return the sorted list of StringChunkField found
Return prefixFields.OrderBy(Function(field) field).ToList()
End Function
End Class
用法
我准备了一些样本类型来测试TypeEmbeddedStringReader类的行为和使用方式。
简单地说,您必须调用 Shared 函数 ReadStringFrom 作为参数传递一个包含要读取的字符串的对象。
以下是样本类型:
Public Class SampleType
Public Prefix_1 As String = "1 to 100 bytes"
Public Prefix_2 As String = "101 to 200 bytes"
Public Prefix_3 As String = "201 to 300 bytes"
Public Prefix_4 As String = "301 to 400 bytes"
End Class
Public Class TypeWithoutString
End Class
Public Class TypeWithNonConsecutiveFields
Public Prefix_1 As String = "1 to 100 bytes"
Public Prefix_5 As String = "101 to 200 bytes"
End Class
Public Class TypeWithInvalidStringBeginning
Public Prefix_2 As String = "1 to 100 bytes"
End Class
这是我用来测试它的主要模块:
Imports TypeEmbeddedStringReader.Samples
Module Module1
Sub Main()
ExtractStringFrom(New TypeWithoutString())
ExtractStringFrom(New TypeWithInvalidStringBeginning())
ExtractStringFrom(New TypeWithNonConsecutiveFields())
ExtractStringFrom(New SampleType())
End Sub
Private Sub ExtractStringFrom(ByVal target As Object)
Try
Dim result As String = TypeEmbeddedStringReader.ReadStringFrom(target)
Console.WriteLine(result)
Catch exception As ArgumentException
Console.WriteLine("Type '{0}': {1}", target.GetType(), exception.Message)
End Try
Console.WriteLine()
End Sub
End Module
以及运行它的结果:
Type 'TypeEmbeddedStringReader.Samples.TypeWithoutString': Does not contains any StringChunkField
Parameter name: target
Type 'TypeEmbeddedStringReader.Samples.TypeWithInvalidStringBeginning': The first StringChunkField found, 'Prefix_2', does not holds the beggining of the string. If holds the beggining of the string, it should be numbered as '1'.
Parameter name: target
Type 'TypeEmbeddedStringReader.Samples.TypeWithNonConsecutiveFields': At least one StringChunkField between 'Prefix_1' and 'Prefix_5' is missing
Parameter name: target
1 to 100 bytes101 to 200 bytes201 to 300 bytes301 to 400 bytes
如果对你有用,请告诉我是否可以为你提供任何其他帮助。
更新
根据 Gens 的要求,我在 TypeEmbeddedStringReader 类中添加了一个函数,以从提供其名称和程序集文件的类型实例中读取字符串:
Public Shared Function ReadStringFromInstanceOf(ByRef assemblyFile As String, ByRef targetTypeName As String)
Dim assembly As Assembly = assembly.LoadFrom(assemblyFile)
Dim targetType As Type = assembly.GetType(targetTypeName)
Dim target As Object = Activator.CreateInstance(targetType)
Return ReadStringFrom(target)
End Function
这是我用于测试的样本类型:
Public Class UnorderedFields
Public Prefix_2 As String = "101 to 200 bytes"
Public Prefix_4 As String = "301 to 400 bytes"
Public Prefix_1 As String = "1 to 100 bytes"
Public Prefix_3 As String = "201 to 300 bytes"
End Class
这是测试它的代码:
Dim assemblyFile As String = Assembly.GetExecutingAssembly()
Dim targetTypeName As String = "TypeEmbeddedStringDemo.UnorderedFields"
Console.WriteLine(TypeEmbeddedStringReader.ReadStringFromInstanceOf(assemblyFile, targetTypeName))
这是上面代码的输出:
1 to 100 bytes101 to 200 bytes201 to 300 bytes301 to 400 bytes
我希望这可以帮助您解决问题。如果您还需要什么,请告诉我!
更新 2
回答 Gens,Simon 的解决方案不起作用的原因是因为正在对字段名称进行比较。下面的例子排序失败(只是为了显示排序问题,除了它是无效的)
Public Class UnorderedFields
Public Prefix_2 As String = "101 to 200 bytes"
Public Prefix_11 As String = "301 to 400 bytes"
Public Prefix_1 As String = "1 to 100 bytes"
Public Prefix_3 As String = "201 to 300 bytes"
End Class
它给出:
1 to 100 bytes**301 to 400 bytes**101 to 200 bytes201 to 300 bytes
修复比较器的实现以使用数字而不是名称:
Public Function Compare(ByVal x As FieldInfo, ByVal y As FieldInfo) As Integer Implements IComparer(Of FieldInfo).Compare
Dim xNumber = Integer.Parse(x.Name.Replace("Prefix_", String.Empty))
Dim yNumber = Integer.Parse(y.Name.Replace("Prefix_", String.Empty))
Return xNumber.CompareTo(yNumber)
End Function
给出正确的结果:
1 to 100 bytes101 to 200 bytes201 to 300 bytes301 to 400 bytes
希望对你有帮助。