我如何以编程方式知道当前代码定义在哪个祖先 class 中?

How can I programmatically know which ancestor class the current code is defined within?

我如何以编程方式知道当前代码在哪个祖先 class 中定义?

使用以下代理代码...

Option Public
Option Declare
%Include "lsprcval.lss" 'defines constants used by GetThreadInfo

Dim gNotesLog As NotesLog

Class MyClass
    Sub New
        On Error GoTo ErrorSub

        gNotesLog.LogAction "Starting " + TypeName(Me) + "." & GetThreadInfo(LSI_THREAD_PROC)
        Error 1, "Forced error"
        Exit Sub
ErrorSub:
        gNotesLog.LogError Err, TypeName(Me) + "." & GetThreadInfo(LSI_THREAD_PROC) & ":" & Erl & " " + Error$
        Error Err, Error$ 'throw error
    End Sub
End Class

Class MyChildClass As MyClass
    Sub New
        On Error GoTo ErrorSub

        gNotesLog.LogAction "Starting " + TypeName(Me) + "." & GetThreadInfo(LSI_THREAD_PROC)
        gNotesLog.LogAction "childish code goes here"
        Exit Sub
ErrorSub:
        gNotesLog.LogError Err, TypeName(Me) + "." & GetThreadInfo(LSI_THREAD_PROC) & ":" & Erl & " " + Error$
        Error Err, Error$ 'throw error
    End Sub
End Class

Sub Initialize
    Set gNotesLog = New NotesLog("ExampleAgent")
    gNotesLog.OpenAgentLog

    On Error Resume Next
    MakeClass
    MakeChildClass
    gNotesLog.LogAction "Agent Complete"
End Sub

Sub MakeClass
    On Error GoTo ErrorSub
    Dim oMyClass As New MyClass
    Exit Sub
ErrorSub:
    gNotesLog.LogError Err, GetThreadInfo(LSI_THREAD_PROC) & ":" & Erl & " " + Error$
    Error Err, Error$ 'throw error
End Sub

Sub MakeChildClass
    On Error GoTo ErrorSub
    Dim oMyChildClass As New MyChildClass
    Exit Sub
ErrorSub:
    gNotesLog.LogError Err, GetThreadInfo(LSI_THREAD_PROC) & ":" & Erl & " " + Error$
    Error Err, Error$ 'throw error
End Sub

日志输出为:

Started running agent 'Whosebug' on 04/10/2017 02:38:24 PM
04/10/2017 02:38:24 PM: Starting MYCLASS.NEW
04/10/2017 02:38:24 PM: Error (1): MYCLASS.NEW:8 Forced error
04/10/2017 02:38:24 PM: Error (1): MAKECLASS:3 Forced error
04/10/2017 02:38:24 PM: Starting MYCHILDCLASS.NEW
04/10/2017 02:38:24 PM: Error (1): MYCHILDCLASS.NEW:8 Forced error
04/10/2017 02:38:24 PM: Error (1): MAKECHILDCLASS:3 Forced error
04/10/2017 02:38:24 PM: Agent Complete
Ran LotusScript code
Done running agent 'Whosebug' on 04/10/2017 02:38:24 PM:
我不喜欢,因为它具有误导性。 "Starting MYCHILDCLASS.NEW" 和以下错误由 MyClass.New 中的代码记录。因为 MyClass.New 抛出那个错误,MyChildClass.New 甚至没有启动!

我更愿意看到:

Started running agent 'Whosebug' on 04/10/2017 02:38:24 PM
04/10/2017 02:38:24 PM: Starting MYCLASS.NEW
04/10/2017 02:38:24 PM: Error (1): MYCLASS.NEW:8 Forced error
04/10/2017 02:38:24 PM: Error (1): MAKECLASS:3 Forced error
04/10/2017 02:38:24 PM: Starting MYCLASS(MYCHILDCLASS).NEW
04/10/2017 02:38:24 PM: Error (1): MYCLASS(MYCHILDCLASS).NEW:8 Forced error
04/10/2017 02:38:24 PM: Error (1): MAKECHILDCLASS:3 Forced error
04/10/2017 02:38:24 PM: Agent Complete
Ran LotusScript code
Done running agent 'Whosebug' on 04/10/2017 02:38:24 PM:
,因为这样我就可以准确地知道哪个 class 包含错误,以及该对象是否是从后代 class 本身定义的。

我知道最简单的解决方案是将 MyClass 更改为:

Class MyClass
    Sub New
        On Error GoTo ErrorSub
        Dim sMe As String

        sMe = TypeName(Me)
        If sMe <> "MYCLASS" Then
            sMe = "MYCLASS(" + sMe + ")"
        End If

        gNotesLog.LogAction "Starting " + sMe + "." & GetThreadInfo(LSI_THREAD_PROC)
        Error 1, "Forced error"
        Exit Sub
ErrorSub:
        gNotesLog.LogError Err, sMe + "." & GetThreadInfo(LSI_THREAD_PROC) & ":" & Erl & " " + Error$
        Error Err, Error$ 'throw error
    End Sub
End Class

但这是在每个方法中对 class 名称进行硬编码。有没有一种方法可以做到这一点,既不需要将 class 名称硬编码到字符串中,也不需要假设每个脚本库的名称一致,其中只有一个 class?

鉴于缺乏答案,我决定提供我的想法。除了尽可能少的例外,我在自己的脚本库中都有每个 class,并且每个脚本库都使用我制作的 "DebuggingScripts" 库,其中包含(除其他外):

  • 一个 LogErrorForObject() 函数,它获取有关错误和调用代码的信息,并将其记录下来。我不包括下面的代码,我只是想指出它的第 5 个参数是对象的名称。

  • a DebuggingScriptsObjectBASE class 我所有其他 class 继承自。这个class有以下方法:

    • Public Function ToString As String - Returns TypeName(Me) 但可以被覆盖。例如,class 可能想要 return Me.Parent.ToString() + ".Child(" & Me.MyName & ")"

    • Private Sub subRegisterModuleForToString(sClassName As String) - 将指定的 class 名称与调用模块(也称为脚本库)相关联。这将从每个 class.

    • New 构造函数子调用
    • Private Function fsToString_FromCodeClass(sCodeClass As String) As String - 给定 class 代码定义的参数,如果 sCodeClass = TypeName(Me) 那么 returns Me.ToString() , 否则 returns sCodeClass + "(" + Me.ToString() + ")" 例如"SuperClassNameWhereCodeIsDefined(ClassName)"

    • Private Function fsToString As String - 如果调用 class 的 New sub 调用 Me.subRegisterModuleForToString(ThisClassName$) 然后调用捕获的信息用于获取 class 名称并将其传递给 Me.fsToString_FromCodeClass。否则,只是 returns Me.ToString()

尽管如此。现在,每当我创建一个新的 class 时,我都会确保它扩展 DebuggingScriptsObjectBASE,然后在 New 构造子中调用 Me.subRegisterModuleForToString(*MyClassName*)。完成后,那些 classes 中的任何 logging/error 处理都可以 use/pass Me.fsToString() 每当他们否则使用 TypeName(Me) (例如 [= 的第 5 个参数32=] 我提到的函数。)如果代码是在任何对象的 superclass 中定义的,则日志会记录该 superclass 的名称以及该对象的任何 ToString() 方法 returns.

我仍然更喜欢这样一种解决方案,我根本不需要硬编码我的 class 的名字,但在每个 class 的构造函数子调用 [=34 中只需要一次=] 可以接受。

这是我在 DebuggingScriptsObjectBASE 对象中提到的代码:

%REM
    Class DebuggingScriptsObjectBASE
<!-- Created Jun 9, 2017 by JSmart523 -->
<!-- If you're reading this, hover your mouse pointer over "DebuggingScriptsObjectBASE" in the Class statement after this comment. -->
    This is intended to be the common ancestor object for all custom objects you write. The benefit is fsToString() and ToString() methods. Other classes and procedures of this script library do not require that your objects inherit from this one.
<br>
    The best way to use it is,<ol>
        <li>Have all of your custom classes inherit (directly or indirectly) from this class.
        <li>Follow the best practice of only having one class per script library, or at least create your script libraries so no classes descend from any classes within the same script library.
        <li>In the constructor sub (New) of each class, call Me.subRegisterModuleForToString(CLASSNAME) where CLASSNAME is the name of the class. (Yes, unfortunately you have to hardcode this. No better way was found, LotusScript is not a completely reflective language and TypeName() doesn't work for parent-classes.)
        <li>When logging errors or other debugging messages and wanting to mention the current object in your message, use Me.fsToString() to generate the name of your object.
        <li>If <code>TypeName(Me)</code> is insufficient for the name of your object, override Me.ToString() (not Me.<em>fs</em>ToString()) to return whatever makes sense.
    </ol>
    If you do this then, any time your code logs something and wants to generate a string representing your object,<ul>
        <li>if the code doing so is defined in the actual class of the object, it will still return Me.ToString(), however,
        <li>if the code doing so is NOT defined in the actual class but in an ancestor class then, Me.fsToString() will return <span style="font-family: monospace; color: black;">sCodeClass <font style="color: blue;">+</font> "(" <font style="color: blue;">+ Me.</font>ToString<font style="color: blue;">()+</font> ")"</span>, for example "ParentClass(ChildClass)" which is much better at describing where the code in question is.</ul>
<br>
    If you don't follow the best practice of only havng one class per script library and instead define classes that inherit other classes within the same script lilbrary (e.g. this script library combines almost all it needs into a single script library so it is more easily packaged), but still want to inherit from this object then do not call Me.subRegisterModuleForToString nor Me.fsToString from within your class, use Me.fsToString_FromCodeClass instead and pass the name of your class as the method's argument.
<br>
    Also included in this class is a LogLevel integer property. You can use this in your coding to determine how verbose your logging should be, with 0 as normal. For example, if your class has a DoTheThing method you might want that method's code to be:
<blockquote>
<pre><div style="font-family: monospace; font-size: 9pt; color: black;"><font style="color: blue;">Class</font> YourClass <font style="color: blue;">As</font> DebuggingScriptsObjectBASE
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font style="color: blue;">Sub New()</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font style="color: blue;">If Not</font> libbDebugMode <font style="color: blue;">Then On Error GoTo</font> LogAndThrowError
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font style="color: blue;">Me.</font>subRegisterModuleForToString<font style="color: blue;">(</font><font style="color: black;">"YourClass"</font><font style="color: blue;">)</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font style="color: blue;">Exit Sub</font>
LogAndThrowError<font style="color: blue;">:</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LogErrorForObject <font style="color: blue;">Err, Erl, Error$, Me.</font>fsToString<font style="color: blue;">(),</font> <font style="color: black;">""</font><font style="color: blue;">,</font> <font style="color: black;">""</font><font style="color: blue;">,</font> <font style="color: black;">""</font><font style="color: blue;">,</font> <font style="color: purple;">True</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font style="color: blue;">End Sub</font>
<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font style="color: blue;">Sub</font> DoTheThing<font style="color: blue;">(</font>db <font style="color: blue;">As</font> <font style="color: black;">NotesDatabase</font><font style="color: blue;">,</font> doc <font style="color: blue;">As</font> <font style="color: black;">NotesDocument</font><font style="color: blue;">,</font> vArg3 <font style="color: blue;">As Variant)</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font style="color: blue;">If Not</font> libbDebugMode <font style="color: blue;">Then On Error GoTo</font> LogAndThrowError
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font style="color: blue;">If Me.</font>LogLevel <font style="color: blue;">&#62;</font> 0 <font style="color: blue;">Then</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font style="color: blue;">Dim</font> oIndent <font style="color: blue;">As New</font> MortalIndent<font style="color: blue;">(</font><font style="color: black;">""</font><font style="color: blue;">,</font> <font style="color: black;">"Starting "</font> <font style="color: blue;">+ Me.</font>fsToString<font style="color: blue;">() +</font> <font style="color: black;">".DoTheThing"</font><font style="color: blue;">,</font> <font style="color: black;">"Exiting "</font> <font style="color: blue;">+ Me.</font>fsToString<font style="color: blue;">() +</font> <font style="color: black;">".DoTheThing"</font><font style="color: blue;">)</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font style="color: blue;">If Me.</font>LogLevel <font style="color: blue;">&#62;</font> 1 <font style="color: blue;">Then</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LogAction <font style="color: black;">"db = "</font>    <font style="color: blue;">+</font> fsStringifyForDebugging<font style="color: blue;">(</font>db<font style="color: blue;">,</font>    <font style="color: purple;">True</font><font style="color: blue;">)</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LogAction <font style="color: black;">"doc = "</font>   <font style="color: blue;">+</font> fsStringifyForDebugging<font style="color: blue;">(</font>doc<font style="color: blue;">,</font>   <font style="color: purple;">True</font><font style="color: blue;">)</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LogAction <font style="color: black;">"vArg3 = "</font> <font style="color: blue;">+</font> fsStringifyForDebugging<font style="color: blue;">(</font>vArg3<font style="color: blue;">,</font> <font style="color: purple;">True</font><font style="color: blue;">)</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font style="color: blue;">End If</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font style="color: blue;">End If</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font style="color: green;">'MAIN CODE WOULD GO HERE</font>
<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font style="color: blue;">Exit Sub</font>
LogAndThrowError<font style="color: blue;">:</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LogErrorForObject <font style="color: blue;">Err, Erl, Error$, Me.</font>fsToString<font style="color: blue;">(),</font> <font style="color: black;">""</font><font style="color: blue;">,</font> ToArray3<font style="color: blue;">(</font>db<font style="color: blue;">,</font> doc<font style="color: blue;">,</font> vArg3<font style="color: blue;">),</font> <font style="color: black;">""</font><font style="color: blue;">,</font> <font style="color: purple;">True</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font style="color: blue;">End Sub</font>
<font style="color: blue;">End Class</font></div></pre>
<!-- Huge thanks to Julian Robichaux's LS2HTML script at http://www.nsftools.com/tips/ls2html.lss -->
</blockquote>
    so that, if an object of that class has a LogLevel property value greater than zero then this script library will log when the method starts, and stops, and indent all of the log entries in the middle, and if the LogLevel value is greater than 1 then it also logs the arguments.
    Also, if another class extends YourClass then the log messages will explain that the code is contained within YourClass, not the descendant class.
%END REM
Public Class DebuggingScriptsObjectBASE

    'Private list of classes, indexed by the module (i.e. script library) they are contained in
    Private plstsClassByModule List As String


    %REM
        Private Sub subRegisterModuleForToString(sClassName As String)
        If you call <code>Me.subRegisterModuleForToString("MyClassName")</code> from your class's New sub, when this specific class calls Me.fsToString() the returned string will indicate that it was called by this specific class. This greatly helps avoid confusion when trying to figure out which class contains the code that logged the error.
        Stores <code>sClassName</code> into a list of class names, indexed by the module (aka script library) that defines them.
<br>
        Unfortunately there's no way for a method to be able to tell which ancestor class or descendant class (if any) called it, but we CAN tell which <em>design element</em> contains the code that called the method. By calling this in each class's constructor sub, your objects build a "Design Element to CodeClass" map for themselves. Then, if Me.fsToString is called, while there isn't any native LotusScript to determine which class defines the code that called it, it can derrive the ClassCode via <code>Me.plstsClassByModule(GetThreadInfo(LSI_THREAD_CALLMODULE))</code>
    <br>
        <b>Arguments</b>
        <blockquote><dl><dt>sClassName As String</dt><dd>Name of the class that is calling this method.</dd></dl></blockquote>
    %END REM
    Private Sub subRegisterModuleForToString(sClassName As String)
        If Not libbDebugMode Then On Error GoTo LogAndThrowError

        Dim sCallModule As String
        sCallModule = GetThreadInfo(LSI_THREAD_CALLMODULE)

        If Len(sClassName) = 0 Then
            Error 13, Me.ToString() + ".subRegisterModuleForToString's argument cannot be blank."
        ElseIf Not IsElement(Me.plstsClassByModule(sCallModule)) Then
            'remember that script library sCallModule defines sClassName
            Me.plstsClassByModule(sCallModule) = sClassName
'           Me.subAddToClassNames sClassName
        ElseIf Me.plstsClassByModule(sCallModule) <> sClassName Then
            Error DebuggingScripts_ERROR_CodeClassConflict, {If both "} + sClassName + {" and "} + Me.plstsClassByModule(sCallModule) + {" are defined within module "} + sCallModule + {", neither should call subRegisterModuleForToString. Please either move one of the classes to its own script libraray or change that class so that all calls to Me.fsToString() instead call Me.fsToString_FromCodeClass("YourClassName")}
        'Else
            'We don't need to do anything. Me.plstsClassByModule(sCallModule) is already sClassName!
        End If
        Exit Sub
LogAndThrowError:
        'Replace error handling with whatever you use
        LogErrorForObject Err, Erl, Error$, "DebuggingScriptsObjectBASE", "", sClassName, sCallModule, True
    End Sub

    %REM
        Public Function ToString As String
        Unless you override this function, returns <code>TypeName(Me)</code>
        You can override this for a class that inherits from this one. For example, if class Foo extends this one, and a Foo object was always in the "foo" property of it's parent object, you might want Foo.ToString() to override this function with the following code:
        <pre><div style="font-family: monospace; font-size: 9pt; color: black;"><font style="color: blue;">Public</font> <font style="color: blue;">Function</font> ToString <font style="color: blue;">As</font> <font style="color: blue;">String</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font style="color: blue;">On</font> <font style="color: blue;">Error</font> <font style="color: blue;">Resume</font> <font style="color: blue;">Next</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ToString <font style="color: blue;">=</font> DebuggingScriptsObjectBASE<font style="color: blue;">.</font><font style="color: blue;">.</font>ToString<font style="color: blue;">(</font><font style="color: blue;">)</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font style="color: blue;">If</font> <font style="color: blue;">Not</font> <font style="color: blue;">Me</font><font style="color: blue;">.</font>Parent <font style="color: blue;">Is</font> <font style="color: purple;">Nothing</font> <font style="color: blue;">Then</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font style="color: blue;">If</font> <font style="color: blue;">Me</font><font style="color: blue;">.</font>Parent<font style="color: blue;">.</font>foo <font style="color: blue;">Is</font> <font style="color: blue;">Me</font> <font style="color: blue;">Then</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ToString <font style="color: blue;">=</font> Parent<font style="color: blue;">.</font>ToString<font style="color: blue;">(</font><font style="color: blue;">)</font> <font style="color: blue;">+</font> <font style="color: black;">".foo"</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font style="color: blue;">End</font> <font style="color: blue;">If</font>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font style="color: blue;">End</font> <font style="color: blue;">If</font>
<font style="color: blue;">End</font> <font style="color: blue;">Function</font></div></pre>
<!-- Huge thanks to Julian Robichaux's LS2HTML script at http://www.nsftools.com/tips/ls2html.lss -->
        If you override, <strong>be <i>sure</i> that your ToString code will not cause an infinite loop.</strong> Within your ToString code this probably means:
        - Do not call Me.ToString()
        - Do not call Me.fsToString()
        - Do not send Me as an argument to fsStringifyForDebugging because it will try the ToString method.
        - Do not send Me as an argument to any logging functions in this script library because they use fsStringifyForDebugging
        - Do not call procedures where, if there's an error, any of the above might happen.
    %END REM
    Public Function ToString As String
        On Error GoTo ErrorSub
        ToString = TypeName(Me)
ExitSub:
        Exit Function
ErrorSub:
        ToString = "¿¿TypeName(Me) failed??"
        Resume ExitSub
    End Function

    %REM
        Private Function fsToString As String
    <!-- Created Jun 9, 2017 by JSmart523 -->
    Returns <code>fsToString_FromCodeClass(Me.plstsClassByModule(GetThreadInfo(LSI_THREAD_CALLMODULE)))</code>
<br>
    If the calling module isn't an index value in the list Me.plstsClassByModule (populated via Me.subRegisterModuleForToString), or the class is the last descendant class for Me, returns <code>Me.ToString()</code>
    Otherwise returns a string like "CodeClass(ObjectToString)" where<ul><li>"CodeClass" is <code>Me.plstsClassByModule(GetThreadInfo(LSI_THREAD_CALLMODULE))</code>, and<li>"ObjectToString" is <code>Me.ToString()</code> (which is usually <code>TypeName(Me)</code>).</ul> 

    This function requires the following assumptions:
        - Your class would have called Me.subRegisterModuleForToString("YourClassName") if you wanted to use this enhanced functionality.
        - The design element that contains your class does not also contain any classes that extend or are an ancestor of this class.
    Calls Me.fsToString_FromCodeClass, Me.ToString
    %END REM
    Private Function fsToString As String
        Dim sModule As String

        If Not libbDebugMode Then On Error GoTo TypeNameError
        fsToString = TypeName(Me)

        If Not libbDebugMode Then On Error GoTo LogAndExit
        sModule = GetThreadInfo(LSI_THREAD_CALLMODULE)
        If IsElement(Me.plstsClassByModule(sModule)) Then
            fsToString = fsToString_FromCodeClass(Me.plstsClassByModule(sModule))
        Else
            fsToString = Me.ToString()
        End If
ExitSub:
        Exit Function

TypeNameError:
        fsToString = "{??Unknown object because fsToStringFailed??}"
        LogErrorForObject Err, Erl, "Error attempting TypeName(Me): " + Error$, "DebuggingScriptsObjectBASE", "", "", "", False
        Resume ExitSub
LogAndExit:
        fsToString = "{??Unknown object because fsToStringFailed??}"
        LogErrorForObject Err, Erl, Error$, Me, "", "", "", False
        Resume ExitSub
    End Function

    %REM
        Private Function fsToString_FromCodeClass(sCodeClass As String) As String
        Compares sCodeClass to TypeName(Me) and
        &nbsp;&nbsp;- if the same, returns Me.ToString
        &nbsp;&nbsp;- if different, returns <code>sCodeClass + "(" + Me.ToString + ")"</code>
    <br>
        <b>Arguments</b>
        <blockquote><dl><dt>sCodeClass As String</dt><dd>Name of the class containing the code in question (as opposed to TypeName(Me) which is just the end class even if the code in question is defined within a class that TypeName(Me) descends from.</dd></dl></blockquote>

        Called by Me.fsToString(), and any objects that want to use this standard of logging the code-defining class <em>and</em> <code>TypeName(Me)</code> in a common format but (due to having ancestor objects in the same script library) Me.fsToString() is not suitable. (For example, for portability, several classes are defined withn this "DebuggingScripts" script library, and all of them inherit from DebuggingScriptsObjectBASE.) 
    %END REM
    Private Function fsToString_FromCodeClass(sCodeClass As String) As String
        On Error Resume Next
        fsToString_FromCodeClass = TypeName(Me) 'in case the next line fails
        fsToString_FromCodeClass = Me.ToString()
        If UCase(sCodeClass) <> TypeName(Me) And Len(sCodeClass) <> 0 Then
            fsToString_FromCodeClass = sCodeClass + "(" + fsToString_FromCodeClass + ")"
        End If
    End Function
End Class