Как получить строки с помощью отражения и объединить их по возрастанию - PullRequest
7 голосов
/ 28 июня 2011

У меня есть длинная строка, которая была разделена на множество небольших строк, используя следующий шаблон:

Public Class Test
    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"
    'and so on
End Class

И это Test class было скомпилировано как проект библиотеки классов (т.е. файл .dll) и сохранено в C: \ Test.dll

Обратите внимание, что я не знаю, сколько строк Prefix_ существовало в файле dll.

Мой вопрос: Как извлечь все строки, которые начинаются с Prefix_ через отражение и объединить их по возрастанию (т.е. Prefix_1 & Prefix_2 ...) в один строка

ОБНОВЛЕНИЕ для вознаграждения:

Щедрость применима только для ответа в решении VB.NET

Ответы [ 6 ]

7 голосов
/ 28 июня 2011

Это должно помочь вам начать. Извините, это C #, но я не помню лямбда-синтаксис.

     Type type = Assembly.LoadFrom (@"c:\test.dll").GetType ("Test");
     object instance = type.GetConstructor (Type.EmptyTypes).Invoke (null);
     var fields = type.GetFields ().Where (f => f.Name.StartsWith ("Prefix_")).OrderBy(f => f.Name);
     string x = fields.Aggregate (new StringBuilder (), (sb, f) => sb.Append((string)f.GetValue (instance)), sb => sb.ToString ());

VB.NET

  Dim type As Type = Assembly.LoadFrom("c:\test.dll").GetType("Test")
  Dim instance As Object = Type.GetConstructor(Type.EmptyTypes).Invoke(Nothing)
  Dim fields = _
     type.GetFields() _
        .Where(Function(f) f.Name.StartsWith("Prefix_")) _
        .OrderBy(Function(f) f.Name)
  Dim bigString As String = _
     fields.Aggregate(New StringBuilder(), _
                      Function(sb, f) sb.Append(DirectCast(f.GetValue(instance), String)), _
                      Function(sb) sb.ToString())
3 голосов
/ 04 июля 2011

Если строки определены в том же порядке, что и в вашем вопросе, вы можете избежать сортировки, и вот простой ответ VB.NET:

Public Function Extract() As String
    Dim type As Type = Assembly.LoadFrom("C:\test.dll").GetType("YourNamespace.Test")
    Dim instance As Object = Activator.CreateInstance(type)
    Dim sb As New StringBuilder
    Dim field As FieldInfo
    For Each field In type.GetFields
        If field.Name.StartsWith("Prefix_") Then
            sb.Append(field.GetValue(instance))
        End If
    Next
    Return sb.ToString
End Function

, иначе здесь есть функция с сортировкой:

Public Function Extract() As String
    Dim type As Type = Assembly.LoadFrom("c:\test.dll").GetType("YourNamespace.Test")
    Dim fields As New List(Of FieldInfo)
    Dim field As FieldInfo
    For Each field In type.GetFields
        If field.Name.StartsWith("Prefix_") Then
            fields.Add(field)
        End If
    Next

    fields.Sort(New FieldComparer)

    Dim sb As New StringBuilder
    Dim instance As Object = Activator.CreateInstance(type)
    For Each field In fields
        sb.Append(field.GetValue(instance))
    Next
    Return sb.ToString
End Function

Private Class FieldComparer
    Implements IComparer(Of FieldInfo)

    Public Function Compare(ByVal x As FieldInfo, ByVal y As FieldInfo) As Integer Implements IComparer(Of FieldInfo).Compare
        Return x.Name.CompareTo(y.Name)
    End Function
End Class
3 голосов
/ 04 июля 2011

Я хотел бы предложить объектно-ориентированное решение на основе вашего ответа в 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

Надеюсь, это поможет.

2 голосов
/ 28 июня 2011

Получил в коде C # (VB.NET немного ржавый :)):

using System;
using System.Linq;
using System.Text;
using System.Reflection;

void ExtractFields()
{
        const string prefix = "Prefix_";
        Assembly assembly = Assembly.LoadFile("C:\\Test.dll");
        Type classTestType = assembly.GetType("Test");
        var classTest = Activator.CreateInstance(classTestType);
        FieldInfo[] fields = classTestType.GetFields(BindingFlags.GetField)
            .Where(m => m.Name.StartsWith(prefix))
            .OrderBy(m => m.Name)
            .ToArray();
        var sb = new StringBuilder();
        foreach (FieldInfo field in fields)
        {
            sb.Append(field.GetValue(classTest));
        }
        string allStringConcatenated = sb.ToString();
}
2 голосов
/ 28 июня 2011

У вас есть открытые поля, поэтому из объекта Type, представляющего класс, получите объекты FieldInfo и исключите те, чье имя не начинается с Prefix_

Если у вас есть такие, вы можете вызвать GetValue для FieldInfo объектов с объектом (ваш экземпляр класса Test) в качестве параметра, чтобы получить значение поля.

Если вам все равно нужно упорядочить результаты, я бы предложил оператор LINQ

Извините, я не знаю VB, иначе я бы написал вам код.

ОБНОВЛЕНИЕ : некоторый код C #

Test myTestInstance = ... // Do stuff to the the instance of your class
Type myType = typeof(Test); // Or call GetType() on an instance
FieldInfo[] myFields = myType.GetFields();
var myPrefixedFields = myFields
                         .Where(fi => fi.Name.StartsWith("Prefix_"))
                         .OrderBy(fi => fi.Name);
string result = string.Empty;
foreach(FieldInfo fi in myPrefixedFields)
{
    // You may prefer to use a string builder.
    result += fi.GetValue(myTestInstance);
}

Это должно быть об этом.

1 голос
/ 08 июля 2011

Используя тестовый класс, слегка измененный, из вашего вопроса:

Public Class Test
  Public Prefix_15 As String = "501 to 600 bytes"
  Public Prefix_5 As String = "401 to 500 bytes"
  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 Function GetPrefixString() As String
  Dim type As Type = Assembly.LoadFrom("C:\test.dll").GetType("Test.Test")
  Dim test As Object = Activator.CreateInstance(type)

  Dim fieldList As New List(Of String)
  For Each field As FieldInfo In _
                    From x In type.GetFields _
                    Where x.Name.StartsWith("Prefix_") _
                    Order By Convert.ToInt32(x.Name.Replace("Prefix_", String.Empty))
    fieldList.Add(field.GetValue(test))
  Next

  Return String.Join(String.Empty, fieldList.ToArray)
End Sub

дает следующие результаты:

*От 1009 * 1 до 100 байтов от 101 до 200 байтов от 201 до 300 байтов от 301 до 400 байтов от 401 до 500 байтов от 501 до 600 байтов
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...