How to extract rows through reflection and combine it in ascending order

I have a long string that has been split into many smaller lines using the following pattern:

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 

And this Test class was compiled as a class library project (i.e. .dll file) and saved in C: \ Test.dll

Note that I do not know how many lines of Prefix_ exist in the dll file.

My question is: How to get all the lines starting with Prefix_ through reflection and combine it in ascending order (i.e. Prefix_1 and Prefix_2 ...) in one line?

UPDATE for generosity:

The bounty is applicable only for the answer in the VB.NET solution

+7
source share
6 answers

It should start. Sorry, C #, but I don't remember the lambda syntax.

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

I would like to offer an object oriented solution based on your answer in Visual Basic according to your request.

Denial of responsibility:

Please keep in mind that I am not a VB.NET developer. The code I provide is tested and works, but certainly needs some improvements in the language.

I assume that you are working with an instance of the Test class, because the fields it provides are not Shared

main idea

Analyzing your requirements, I found that it is important:

  • Centralize the field naming strategy in one place so your solution can be useful
  • Take care of sorting the fields. If you have prefix_1, prefix_2, and prefix_11, you may not sort it correctly: Prefix_1, Prefix_11, and Prefix_2.
  • Confirm the absence of the names of the missing fields (for example, the transition from prefix_1 to prefix_3)

From what you requested, I modeled each of the fields containing the pieces of string in a class called StringChunkField . This class models each prefix field that contains a piece of string and carries the following responsibilities:

  • Provide information about the field itself: name, number, piece of string that contains, and the number of characters in it
  • Centralize the format and numbering information used to designate the fields. The prefix for the search is defined here, and the number begins with the field names.
  • From the previous paragraph, he can answer whether the field is the starting line or not, and whether the field is StringChunkField or not.
  • Implements IComparable to centralize sorting logic in one place (based on 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 number Public ReadOnly Property Number() As Integer Get Return _number End Get End Property ' Returns the field 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 

Now we have defined what StringChunkField , what name prefix to use and how to build it, we can request an object for the string it contains with an instance of the TypeEmbeddedStringReader class.

Responsibility for this:

  • Find all StringChunkFields in an object
  • Confirm if the numbering of the found fields starts according to the base defined in StringChunkField , and if the numbers are consecutive
  • Rebuild inline string into object from StringChunkField 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 

Using

I prepared several sample types to test the behavior of the TypeEmbeddedStringReader class and how it is used. You just have to call the Shared ReadStringFrom function, passing an object containing the line to read as an argument.

Here are examples of types:

  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 

Here is the main module that he used for testing:

  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 

And the results of its launch:

  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 

Please let me know if you work for you, and if I can help you.

Update

At the request of Gens, I added a function to the TypeEmbeddedStringReader class to read a string from an instance of a type containing its name and assembly file:

  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 

Here is an example of the type I used for testing:

  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 

Here is the code that tests it:

  Dim assemblyFile As String = Assembly.GetExecutingAssembly() Dim targetTypeName As String = "TypeEmbeddedStringDemo.UnorderedFields" Console.WriteLine(TypeEmbeddedStringReader.ReadStringFromInstanceOf(assemblyFile, targetTypeName)) 

This is the output from the above code:

  1 to 100 bytes101 to 200 bytes201 to 300 bytes301 to 400 bytes 

Hope this helps you solve your problem. Please tell me if you need anything else!

Update 2

In answer to a Gens question, the reason the Simon solution does not work is because the comparison is done by the field name. The following example fails to arrange it (just to show the sorting problem, moreover, it is invalid)

  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 

He gives:

  1 to 100 bytes**301 to 400 bytes**101 to 200 bytes201 to 300 bytes 

Committing a comparison implementation to use numbers instead of names:

  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 

It gives the correct result:

  1 to 100 bytes101 to 200 bytes201 to 300 bytes301 to 400 bytes 

Hope this helps.

+3
source

If the strings are defined in the same order as in your question, you can avoid sorting, and here is a simple VB.NET answer:

 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, it is a sorted 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
source

You have public fields, so from the Type object representing the class, get FieldInfo objects and exclude those whose name does not start with Prefix_

After that, you can call GetValue in FieldInfo objects with an object (your instance of the Test class) as a parameter to get the field value.

If you need to order the results anyway, I would suggest the LINQ operator

Sorry, I don’t know VB, otherwise I will write you a code.

UPDATE : some C # code

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

It should be about him.

+2
source

Got it in C # code (VB.NET is a bit rusty :)):

 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
source

Using a test class slightly modified from your question:

 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 

Perform the following function:

 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 

gives the following results:

from 1 to 100 bytes101 to 200 bytes201 to 300 bytes 301 to 400 bytes 401 to 500 bytes501 to 600 bytes

+1
source

All Articles