Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>I'd like to propose an object oriented solution based on your answer, in Visual Basic as you requested.</p> <p><strong>Disclaimer:</strong></p> <p>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.</p> <p>I'm assuming that you are working with an <em>instance</em> of the class <code>Test</code>, because the fields it exposes are not <code>Shared</code></p> <p><strong>Main Idea</strong></p> <p>Analyzing you requirements I found that is important to:</p> <ul> <li>Centralize the naming strategy on of the fields in one place in order for your solution to be mantenible</li> <li>Take care of the sorting of the fields. If you have Prefix_1, Prefix_2 and Prefix_11 you can get then sorted in a <em>wrong</em> way: Prefix_1, Prefix_11 and Prefix_2.</li> <li>Validate there are no missing field names (i.e. jump from Prefix_1 to Prefix_3)</li> </ul> <p>From what you asked, i modeled each of the fields holding the chunks of the string in a class named <code>StringChunkField</code>. This class models each a prefixed field holding a chunk of the string and has the following responsibilities:</p> <ul> <li>Provide information about the field itself: name, number, chunk of the string that holds and the number of characters in it</li> <li>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.</li> <li>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.</li> <li><p>Implements IComparable to centralize sorting logic in one place (it's based on the field number)</p> <pre><code>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 </code></pre></li> </ul> <p>Now we have defined what is a <code>StringChunkField</code>, 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 <code>TypeEmbeddedStringReader</code> class.</p> <p>The responsibilities for it are:</p> <ul> <li>Find all the <code>StringChunkFields</code> presents in an object</li> <li>Validate whether the numbering of the fields found starts according to the base defined in <code>StringChunkField</code> and if the numbers are consecutive</li> <li><p>Rebuild the embedded string in the object from the <code>StringChunkField</code>s values</p> <pre><code>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 </code></pre></li> </ul> <p><strong>Usage</strong></p> <p>I prepared some sample types to test the behavior of the <code>TypeEmbeddedStringReader</code> class and the way to use. Simply, you have to call the <code>Shared</code> function <code>ReadStringFrom</code> passing as argument an object containing the string to read from.</p> <p>Here are the sample types:</p> <pre><code> 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 </code></pre> <p>Here is the main Module i used to test it:</p> <pre><code> 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 </code></pre> <p>And the results from running it:</p> <pre><code> 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 </code></pre> <p>Please let me know if worked for you and if i can be of any other help to you.</p> <p><strong>Update</strong></p> <p>As requested by Gens, i added a function to the <code>TypeEmbeddedStringReader</code> class to read a string from an instance of a type providing it's name and assembly file:</p> <pre><code> 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 </code></pre> <p>Here is the sample type i used for testing:</p> <pre><code> 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 </code></pre> <p>Here is the code that tests it:</p> <pre><code> Dim assemblyFile As String = Assembly.GetExecutingAssembly() Dim targetTypeName As String = "TypeEmbeddedStringDemo.UnorderedFields" Console.WriteLine(TypeEmbeddedStringReader.ReadStringFromInstanceOf(assemblyFile, targetTypeName)) </code></pre> <p>This is the output from the code above:</p> <pre><code> 1 to 100 bytes101 to 200 bytes201 to 300 bytes301 to 400 bytes </code></pre> <p>I hope this helped you to solve your problem. Please tell me if you need anything else!</p> <p><strong>Update 2</strong></p> <p>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)</p> <pre><code> 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 </code></pre> <p>It gives:</p> <pre><code> 1 to 100 bytes**301 to 400 bytes**101 to 200 bytes201 to 300 bytes </code></pre> <p>Fixing the comparer's implementation to use numbers instead of names:</p> <pre><code> 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 </code></pre> <p>Gives the right result:</p> <pre><code> 1 to 100 bytes101 to 200 bytes201 to 300 bytes301 to 400 bytes </code></pre> <p>Hope it helps.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload