Я хотел бы предложить объектно-ориентированное решение на основе вашего ответа в Visual Basic по вашему запросу.
Отказ от ответственности:
Пожалуйста, имейте в виду, чтоЯ не разработчик VB.NET.Код, который я предоставляю, протестирован и работает, но, безусловно, нуждается в некоторых языковых улучшениях.
Я предполагаю, что вы работаете с экземпляром класса Test
, потому что поляэто не Shared
Основная идея
Анализируя ваши требования, я обнаружил, что важно:
- Централизация стратегии именованияна одном из полей в одном месте, чтобы ваше решение было готовым
- Позаботьтесь о сортировке полей.Если у вас есть Префикс_1, Префикс_2 и Префикс_11, вы можете затем отсортировать их неправильно способом: Префикс_1, Префикс_11 и Префикс_2.
- Проверить, нет ли пропущенных имен полей (то есть перейти от Префикса_1 к Префиксу_3)
Исходя из того, что вы спросили, я смоделировал каждое из полей, содержащих фрагменты строки, в классе с именем StringChunkField
.Этот класс моделирует каждое префиксное поле, содержащее фрагмент строки, и имеет следующие обязанности:
- Предоставляет информацию о самом поле: имя, номер, фрагмент строки, которая содержит, и количество символовв нем
- Централизуется информация о формате и нумерации, используемая для именования полей.Здесь определен префикс для поиска, и начинается число с именами поля.
- Из предыдущего элемента он может ответить, является ли поле одним из начинающих строку или нет, и является ли полеa 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
, и являются ли числа последовательными Перестроить встроенную строку в объекте из значений StringChunkField
s
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
Отвечая Генсу, причина, по которой решение Саймона не работает, заключается в том, что сравнение выполняется наИмя поляВ следующем примере происходит сбой в его порядке (просто чтобы показать проблему сортировки, кроме того, что она недействительна)
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
Надеюсь, это поможет.