如何通过反射检索字符串并按升序连接它们

7

我有一个很长的字符串,按照以下模式分成了许多较小的字符串:

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

这个测试类已经编译为类库项目(即.dll文件)并保存在C:\ Test.dll中。

请注意,我事先不知道dll文件中有多少个Prefix_字符串。

我的问题是:如何通过反射检索所有以Prefix_ 开头的字符串,并按升序连接它们(即Prefix_1&Prefix_2 ...)成一个字符串?

悬赏更新:

悬赏仅适用于VB.NET解决方案的答案


嗨,Gens!我刚刚回答了你的问题。根据你所说,我假设你必须使用类Test的一个实例,因为它公开的字段不是共享的。这样对吗?如果不是,请告诉我,我会更新我的答案。谢谢! - nick2083
为什么你不使用数组? - Lightness Races in Orbit
@Gens,你最终解决了你的问题吗?期待得知。再见! - nick2083
6个回答

7

这应该能帮助您入门。很抱歉它是C#,但我不记得lambda语法。

     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())

如果他们也像其他人一样称之为“reduce”就好了... +1 - user541686
为什么要使用 Type.GeConstructor().Invoke() 而不是 Activator.CreateInstance()?此外,如果字段命名为例如 Prefix_9Prefix_10,则这种方法将无法正常工作。 - svick

3
我想提出一个面向对象的解决方案,基于你的答案,使用你要求的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 StringChunkFields 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

希望它能帮到您。

看看 Simon Mourier 的第一个解决方案。我想要类似的东西。但是 Simon Mourier 的第二个解决方案没有起作用。如果你能比 Simon Mourier 更快地解决它,那么赏金就是你的! - Predator
谢谢Gens。我刚刚更新了我的解决方案以满足您的要求。只是想说,我的解决方案没有您在Simon的帖子中描述的排序问题。你试过了吗?再见! - nick2083

3
如果字符串的顺序与您的问题中定义的顺序相同,您可以避免排序,这里是一个简单的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

otherwise here is a function with sorting:

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

你的第一部分解决方案可行。但是我想要你的第二个解决方案,它目前无法正常工作。可能是排序出了问题。连接的字符串与原始字符串不匹配。请改进第二个解决方案,这样我就可以授予你赏金 :) - Predator

2
您有一些公共字段,因此,从表示类的Type对象中获取FieldInfo对象,并排除那些名称不以Prefix_开头的字段。
一旦你有了这些,你可以调用FieldInfo对象上的GetValue方法,将对象(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);
}

大概就是这样了。


2

以下是 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();
}

1

使用一个稍作修改的测试类,来自您的问题:

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

产生以下结果:

1到100字节101到200字节201到300字节301到400字节401到500字节501到600字节


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接