我想提出一个面向对象的解决方案,基于你的答案,使用你要求的Visual Basic语言。
免责声明:
请注意,我不是VB.NET开发人员。我提供的代码经过测试和运行,但肯定需要一些特定语言的改进。
我假设您正在使用“Test”类的实例,因为它公开的字段不是“Shared”。
主要思路:
分析您的要求后,我发现重要的是:
1. 将字段的命名策略集中在一个地方,以便您的解决方案易于维护。
2. 处理字段的排序。如果您有Prefix_1、Prefix_2和Prefix_11,则可能会以错误的方式进行排序:Prefix_1、Prefix_11和Prefix_2。
3. 验证没有缺少的字段名称(即从Prefix_1跳到Prefix_3)。
根据您的要求,我将每个保存字符串块的字段建模为一个名为StringChunkField的类。这个类模拟了每个带有前缀的字段,保存了字符串块,并具有以下职责:
- Provide information about the field itself: name, number, chunk of the string that holds and the number of characters in it
- Centralize information about the format and numbering used to name the fields. Here is defined the prefix to look for, and the number at with the field's names starts.
- From the previous item, it can answer whether a field is the one beginnig a string or not, and whether a field is a StringChunkField or not.
Implements IComparable to centralize sorting logic in one place (it's based on the field number)
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
类的实例查询对象包含的字符串。
它的职责包括:
- Find all the
StringChunkFields
presents in an object
- Validate whether the numbering of the fields found starts according to the base defined in
StringChunkField
and if the numbers are consecutive
Rebuild the embedded string in the object from the StringChunkField
s values
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
希望它能帮到您。