I have a long string that has been divided into lots of smaller Strings 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
has been compiled as class library project (i.e. a .dll file) and saved to C:\Test.dll
Please note that I have no prior knowledge of how many Prefix_
string existed in the dll file.
My question is: How to retrieve all strings that start with Prefix_
via reflection and concatenate it ascending-ly (i.e. Prefix_1 & Prefix_2 ... ) into a single string?
UPDATE for bounty:
Bounty only applicable for answer in VB.NET solution
You concatenate strings by using the + operator. For string literals and string constants, concatenation occurs at compile time; no run-time concatenation occurs. For string variables, concatenation occurs only at run time.
The Java String concat() method concatenates one string to the end of another string. This method returns a string with the value of the string passed into the method, appended to the end of the string.
This ought to get you started. Sorry it's 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())
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 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
I'd like to propose an object oriented solution based on your answer, in Visual Basic as you requested.
Disclaimer:
Please bear in mind that i'm not a VB.NET developer. The code i provide is tested and running, but surely needs some language specific improvements.
I'm assuming that you are working with an instance of the class Test
, because the fields it exposes are not Shared
Main Idea
Analyzing you requirements I found that is important to:
From what you asked, i modeled each of the fields holding the chunks of the string in a class named StringChunkField
.
This class models each a prefixed field holding a chunk of the string and has the following responsibilities:
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
Now we have defined what is a StringChunkField
, what name prefix to use and how to build one, we can query an object for the string it contains with an instance of the TypeEmbeddedStringReader
class.
The responsibilities for it are:
StringChunkFields
presents in an objectStringChunkField
and if the numbers are consecutiveRebuild the embedded string in the object from the StringChunkField
s 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
Usage
I prepared some sample types to test the behavior of the TypeEmbeddedStringReader
class and the way to use.
Simply, you have to call the Shared
function ReadStringFrom
passing as argument an object containing the string to read from.
Here are the sample 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 i used to test it:
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 from running it:
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 worked for you and if i can be of any other help to you.
Update
As requested by Gens, i added a function to the TypeEmbeddedStringReader
class to read a string from an instance of a type providing it's 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 the sample 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 code above:
1 to 100 bytes101 to 200 bytes201 to 300 bytes301 to 400 bytes
I hope this helped you to solve your problem. Please tell me if you need anything else!
Update 2
Answering to Gens, the reason why Simon's solution is not working is because the comparison is being done on the field name. The following example fails in its ordering (just to show the sorting problem, besides it's 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
It gives:
1 to 100 bytes**301 to 400 bytes**101 to 200 bytes201 to 300 bytes
Fixing the comparer's 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
Gives the right result:
1 to 100 bytes101 to 200 bytes201 to 300 bytes301 to 400 bytes
Hope it helps.
You have public fields so, From the Type
object representing the class get the FieldInfo
objects and exclude those whose name doesn't start with Prefix_
Once you have those you can then call GetValue
on the FieldInfo
objects with the object (your instance of class Test
) as the parameter to get the value of the field.
If you need to order the results in anyway, then I'd suggest a LINQ statement
Sorry, I don't know VB otherwise I'd write you some 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);
}
That should be about it.
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();
}
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
Running 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
yields the following results:
1 to 100 bytes101 to 200 bytes201 to 300 bytes301 to 400 bytes401 to 500 bytes501 to 600 bytes
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With