【问题标题】:How to retrieve strings via reflection and concatenate it ascending-ly如何通过反射检索字符串并将其升序连接
【发布时间】:2011-06-28 12:00:51
【问题描述】:

我有一个长字符串,它使用以下模式分成许多较小的字符串:

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

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

我的问题是:如何通过 reflection 检索所有以Prefix_ 开头的字符串并将其升序连接(即 Prefix_1 和 Prefix_2 ... )到一个字符串?

赏金更新:

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

【问题讨论】:

  • 各位好!我刚刚发布了你的问题的答案。根据您的说法,我假设您必须使用 Test 类的实例,因为它公开的字段不是共享的。这是正确的吗?如果没有,请告诉我,以便我更新我的答案。谢谢!
  • 为什么不使用数组?
  • @Gens,你能最终解决你的问题吗?期待知道。再见!

标签: .net vb.net string


【解决方案1】:

这应该可以帮助您入门。抱歉,它是 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
  • 你为什么用Type.GeConstructor().Invoke()而不是Activator.CreateInstance()?此外,如果字段被命名为例如,这将无法正常工作。 Prefix_9Prefix_10.
【解决方案2】:

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

【讨论】:

  • 您的第 1 部分解决方案正在运行。但我想要你的第二个解决方案不起作用。可能排序有问题。连接的字符串与原始字符串不匹配。请改进第二个解决方案,以便我可以将赏金奖励给您:)
【解决方案3】:

我想根据您的回答提出一个面向对象的解决方案,按照您的要求使用 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

希望对你有帮助。

【讨论】:

  • 参见 Simon Mourier 第 1 部分解决方案。我想要那样的东西。但是 Simon Mourier 第 2 部分的解决方案不起作用。如果你能比 Simon Mourier 更快地解决它,那么赏金就是你的了!
  • 感谢 Gens。我刚刚更新了我的解决方案以满足您的要求。只是想说我的解决方案没有你在西蒙的帖子中描述的排序问题。你设法尝试了吗?再见!
【解决方案4】:

你有公共字段,所以,从代表类的 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);
}

应该是这样的。

【讨论】:

    【解决方案5】:

    在 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();
    }
    

    【讨论】:

      【解决方案6】:

      使用您的问题中稍作修改的测试类:

      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 bytes501 到 600 字节

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-05-08
        • 1970-01-01
        • 2010-09-16
        • 2015-06-06
        • 2021-09-27
        相关资源
        最近更新 更多