none
CallByName() function for variables and constants... RRS feed

  • Question

  • Does there exist, or does any one know of, a function which works very similar to 'CallByName()' - but for selecting variables and/or constants based on a string value? We have many modules and classes, each containing roughly 100 or so constants. I can't post any factual sample code here of what I'm doing because I'm a military contractor and they sort of look down on me doing that. But, the gist is that sometimes in code, we don't know which variable or constant to reference or return in a function until we either get input from a user or another function tells us.

    Here is a mock example of what we're trying to do...

    Private Const m_ERR_1000_ENUS As System.String = "My error message in English (U.S.A.) for error number 1000."

    Private Const m_ERR_1000_ESES As System.String = "My error message in Spanish (Spain) for error number 1000."

    Private Const m_ERR_1001_ENUS As System.String = "My error message in English (U.S.A.) for error number 1001."

    Private Const m_ERR_1001_ESES As System.String = "My error message in Spanish (Spain) for error number 1001."

    Private Const m_ERR_1002_ENUS As System.String = "My error message in English (U.S.A.) for error number 1002."

    Private Const m_ERR_1002_ESES As System.String = "My error message in Spanish (Spain) for error number 1002."

    Public Sub Main()

        MsgBox(GetErrMsg(1001, 1034))

    End Sub

    'Returns an error message string based on the supplied error number and system locale...

    Friend Sub GetErrMsg(ByVal ErrNumber As System.Int32, ByVal Locale As System.Int32) As System.String

        Dim strErrMbr As System.String 'Retains the name of the member (constant) to be selected... 

        Dim strLocale As System.String 'Retains the locale identifier...

        Select Case Locale 'Determine the specified locale...

            Case 1033 'English (U.S.A.)…

                strLocale = "ENUS"

            Case 1034 'Spanish (Spain)…

                strLocale = "ESES"

        End Select

        strErrMbr = strErrMbr.Concat("m_ERR_", ErrNumber, "_", strLocale) 'Build the name of the constant to be selected...

       

        'Choose which constant to reference based on the string's value...

        Return MemberByName(strErrMbr) 'Reference the appropriate constant and return its literal value (error message)...

    End Sub

    If "strErrMbr" ends up as "m_ERR_1001_ESES", then the statement "Return MemberByName(strErrMbr)" would be identical to "Return m_ERR_1001_ESES", and so on.  MsgBox(MemberByName(strErrMbr)) and MsgBox(m_ERR_1001_ESES) would be equal and an return the same message string of: "My error message in Spanish (Spain) for error number 1001."

    Now... before anyone posts 100 different ways to handle localization - localization is NOT what we're trying to achieve. As I said, I can't post any sample of what we're actually trying to do.  Also, what we currently have is a really HUGE function with a very long Select Case structure that works something similar to:

    Select Case strErrMbr

        Case "m_ERR_1000_ENUS"

            Return m_ERR_1000_ENUS

        Case "m_ERR_1000_ESES"

            Return m_ERR_1000_ESES

        Case "m_ERR_1001_ENUS"

            Return m_ERR_1001_ENUS

        Case "m_ERR_1001_ESES"

            Return m_ERR_1001_ESES

    End Select

    It works very well, but is an extremely HUGE function. And, each time we add a new constant (along with its literal value) to our code, we have to also add it to inside the function as a possible return item.

     So... to lessen our code, and therefore, the size of our source files, is there a function like "MemberByName()" in existence?  If not, WHY NOT? Microsoft needs to create one, like now, that works for both variables and constants.  Or, does any one have any ideas as to going about creating such a function?

    We have some constraints.  a. Because we use proprietary/military obfuscation software, using dictionaries or external XML files (or other non-encryptable external/support files) are NOT options, so don't even waste space suggesting either.  Any solutions must be hard-coded.  b. Creating dynamic members (constant and/or variables on the fly) is not an option either.

    Anyone who has some ideas on how to create such a function, we'd love to hear from you.  And... GO!

    • Edited by GypsyPrince2k Saturday, September 8, 2018 4:26 PM Better visual clarity.
    Saturday, September 8, 2018 4:23 PM

All replies

  • Given your limitations you can use reflection and pay the minor overhead cost.  You can mitigate the cost by caching the type instance or creating an internal dictionary of temporary cached values.

    Assuming you keep (or refactor) the code to get the strErrMbr string member name, you can use code something like this to get the value of the member by name:

    Public Class MockExample
        Private Const m_ERR_1000_ENUS As System.String = "My error message in English (U.S.A.) for error number 1000."
        Private Const m_ERR_1000_ESES As System.String = "My error message in Spanish (Spain) for error number 1000."
        Private Const m_ERR_1001_ENUS As System.String = "My error message in English (U.S.A.) for error number 1001."
        Private Const m_ERR_1001_ESES As System.String = "My error message in Spanish (Spain) for error number 1001."
        Private Const m_ERR_1002_ENUS As System.String = "My error message in English (U.S.A.) for error number 1002."
        Private Const m_ERR_1002_ESES As System.String = "My error message in Spanish (Spain) for error number 1002."
    
        Private Shared thisType As Type = GetType(MockExample)
    
        Public Shared Function MemberByName(memberName As String) As String
            Dim info = thisType.GetField(memberName, Reflection.BindingFlags.Static Or Reflection.BindingFlags.NonPublic)
            Return info.GetValue(Nothing)
        End Function
    End Class
    
    Note that the GetField parameters are based on the member type and the code existing within the same class.  But you could reflect some other type or access members of a different kind or scope if needed.


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    • Proposed as answer by Cherkaoui.Mouad Wednesday, September 12, 2018 8:35 PM
    Saturday, September 8, 2018 8:08 PM
    Moderator
  • Here's an expanded example with caching:

    Public Class MockExample
        Private Const m_ERR_1000_ENUS As System.String = "My error message in English (U.S.A.) for error number 1000."
        Private Const m_ERR_1000_ESES As System.String = "My error message in Spanish (Spain) for error number 1000."
        Private Const m_ERR_1001_ENUS As System.String = "My error message in English (U.S.A.) for error number 1001."
        Private Const m_ERR_1001_ESES As System.String = "My error message in Spanish (Spain) for error number 1001."
        Private Const m_ERR_1002_ENUS As System.String = "My error message in English (U.S.A.) for error number 1002."
        Private Const m_ERR_1002_ESES As System.String = "My error message in Spanish (Spain) for error number 1002."
    
        Private Shared thisType As Type = GetType(MockExample)
        Private Shared resolvedMembers As New Dictionary(Of String, String)
    
        Public Shared Function MemberByName(memberName As String) As String
            If Not resolvedMembers.ContainsKey(memberName) Then
                Dim info = thisType.GetField(memberName, Reflection.BindingFlags.Static Or Reflection.BindingFlags.NonPublic)
                resolvedMembers(memberName) = info.GetValue(Nothing)
            End If
            Return resolvedMembers(memberName)
        End Function
    End Class
    


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    • Proposed as answer by Cherkaoui.Mouad Wednesday, September 12, 2018 8:35 PM
    Saturday, September 8, 2018 8:09 PM
    Moderator
  • GypsyPrince2k,

    I don't know of a function that wraps it up, but you can do this with reflection in .Net today. I'm showing it with a separate class to hold the constants, but it would work if you have everything in the same class as your code. You just have to call gettype() on the type that holds the fields you want. 

    You'll need to have the following import statement:

    Imports System.Reflection

    First, I have a class that contains the constants

    Public Class ENConstants
        Public Const EN_US_1100 = "Hello"
        Public Const EN_US_2000 = "World"
    
    End Class

    Next, a class with a method that will extract the value of the member field that matches on name

    ''' <summary>
    ''' Message Repository for getting messages from member fields based on reflection 
    ''' </summary>
    Public Class MessageRepo
        Public Function GetMessage(locale As String, code As Integer) As String
            Dim result As String = Nothing
    
            'Decide what class to get the member from, based on inputs
            Dim theLocaleType As Type = Nothing
            If locale = "EN_US" Then
                theLocaleType = GetType(ENConstants)
            ElseIf locale = "Something else" Then
                'pick the appropriate class for this locale
            End If
    
            'Build the member name, and get it's value via reflection
            If theLocaleType IsNot Nothing Then
    
                Dim theFieldName As String = locale & "_" & code.ToString()
    
                'This is specific to getting a field. Other methods exist in System.Type to get other member types
                Dim field = theLocaleType.GetField(theFieldName)
                result = CStr(field.GetValue(field))
            End If
    
            Return result
        End Function
    End Class

    Finally, code that shows how to use the method to call and get a value from a member name calculated at runtime

    Public Class GetMemberFieldByReflectionExample
    
        Public Sub GetMessage()
            Dim repo As New MessageRepo
    
            Dim myString = repo.GetMessage("EN_US", 2000)
    
            Debug.WriteLine(myString)
        End Sub
    End Class



    • Edited by pk21971 Saturday, September 8, 2018 8:39 PM Added Reflection Import
    Saturday, September 8, 2018 8:30 PM