Private this As TSomething

A post on Code Review recently caught my attention (emphasis mine):

If you are setting up a class, don’t encapsulate a Type inside of it – you are only repeating what a class does! I am not sure where this anti-pattern comes from.

The author of these words didn’t use the term “anti-pattern” in the same way I would have… They didn’t mean it as the toxic coding practices I use it for (I know, I asked!). But they aren’t seeing the benefits of it, and ultimately consider it clutter… and that’s where we disagree, regardless of whether “anti-pattern” is incendiary wording or not.

If you’ve been reading this blog for some time, you’ve probably noticed this rather consistent (VBA code written before 2015 doesn’t count!) pattern in my writing of class modules: whenever I need a class, I start by declaring a Private Type for its private instance fields, always named after the class module itself and prefixed with an admittedly rather “Hungarian” T prefix; then the only actual private field in the class is a Private this variable, like this:

Option Explicit
Private Type TPerson
FirstName As String
LastName As String
End Type
Private this As TPerson

Public Property Get FirstName() As String
FirstName = this.FirstName
End Property

Public Property Let FirstName(ByVal value As String)
this.FirstName = value
End Property

Public Property Get LastName() As String
LastName = this.LastName
End Property

Public Property Let LastName(ByVal value As String)
this.LastName = value
End Property

The same class module would “normally” look something like this:

Option Explicit
Private mFirstName As String
Private mLastName As String

Public Property Get FirstName() As String
FirstName = mFirstName
End Property

Public Property Let FirstName(ByVal pFirstName As String)
mFirstName = pFirstName
End Property

Public Property Get LastName() As String
LastName = mLastName
End Property

Public Property Let LastName(ByVal pLastName As String)
mLastName = pLastName
End Property

Yes, it’s less code. So what’s my problem with it?

Several things.

  • Properties and their respective backing field don’t (can’t) use the same identifier.
  • That m prefix is pure clutter that’s only there to say “hey look, this is a private field /module variable!” – in other words, it’s Systems Hungarian notation and does nothing other than increase the cognitive load. Even worse with an underscore, which wrecks the consistent camelCase/PascalCase conventions of literally everything written in any VB dialect.
  • It’s not true that using such Hungarian prefixes helps with autocompletion and IntelliSense. If the class has 5 properties that happen to start with a M, then your 5 backing fields are intertwined with 10 public members (so, drowned, really) that also start with an M.
  • Mutator parameters aren’t consistent either. That p prefix is just as annoying, and I’ll go as far as to say that this m-for-member and p-for-parameter convention is exactly what’s behind the fact that many VBA programmers have never dared implementing a class module “because it’s too confusing” and hard to follow.
  • The locals debugging toolwindow becomes cluttered with all the private fields duplicating the Property Get membersvalues.
mFields-locals
The Locals toolwindow, showing fields and properties as members of Me.

With my “anti-pattern”, there’s a little bit more code, yes. But:

  • Properties and their respective backing field consistently use the same identifier. IntelliSense / autocomplete for my fields consistently only ever includes the backing fields, and all I had to do was to type this..
  • No need for any Hungarian prefix anywhere. I use T for the type declaration (I also use I for interfaces, like in .NET and most C-based languages), because I find that using the class identifier (which would be perfectly legal) would be potentially confusing in Private this As Class1, since in any other context (outside the class module itself) the identifier Class1 in an As clause would be referring to the Class1 class.
  • Parameter names are always explicitly passed ByVal and named value. Yes, this makes Range.Value show up as Range.value, but VBA being case-insensitive, it makes no difference whatsoever. I could have used any other identifier, but value is what VB.NET and C# use; besides RHS isn’t quite as sexy, if more semantically correct. But naming parameters after the property member is an objectively horrible idea; all you see is a soup of mFoo, pFoo and Foo with assignment operators in between.
  • The locals debugging toolwindow now nicely regroups all the fields under this, so the object’s state is much easier to browse and understand at a glance.
  • If you ever need to serialize an object’s state to a binary file, then all you need to do is to Put #fileHandle this and you’re done. The inverse process is just as simple: no need to enumerate the properties one by one, convert them, or manipulate them in any way.
TPerson-locals
The Locals toolwindow, showing properties as members of Me, and a collapsed this member encapsulating the otherwise redundant fields.

I’d love to hear exactly what’s wrong with this “anti-pattern” of mine – I’ve grown pretty fond of it in the past couple years, and until someone can show me how and why I’m actively hurting something somewhere with it, I’ll keep using it in my own code, and posting Code Review and Stack Overflow answers featuring it.. and my blog posts will keep using it too.

One concern raised, was that a UDT doesn’t play well with collections. But this UDT isn’t going to end up in a collection anytime soon – and even if the class instance went into a collection, the encapsulated UDT couldn’t care less: all it does is regrouping the class’ internal state. Code outside the class doesn’t know about it, and couldn’t if it wanted.

You might be worried that a UDT incurs additional overhead… but it doesn’t: it simply provides a convenient structure to organize the private fields of a class. Two Long private fields allocate 4 bytes each and total 8 bytes; a UDT with two Long members allocates a total of 8 bytes, as Len(this) shows. What’s an easy way to know how much space the instance fields of a class take up?

Rubberduck has an encapsulate field refactoring that makes a public field private, renames it, and introduces Property Get and appropriate Property Let/Set mutators for it.

For a while I’ve been considering implementing a feature that builds on this Private Type [anti?] pattern, but held back because I didn’t want Rubberduck to enforce my coding style… although… I would love to be able to just declare my private type and my this private field, parse, and then right-click the UDT field and have Rubberduck generate all the Property Get/Let/Set boilerplate for me.

Would that make it more compelling?

Advertisements

8 thoughts on “Private this As TSomething”

  1. I’ve been using ‘this’ style since I first saw it here on Rubberduck . I think it’s great, it makes the code so much easier to read/write. By just typing ‘this.’ I get a list of all my private fields in IntelliSense.

    I wish I could declare my ‘WithEvents’ variables there as well. 🙂

    Keep up the great work Mat!

    Liked by 1 person

  2. I always wondered what exactly “this” was used for in your example of the self creating classes, thanks to this post I can see the benefits of it now!
    Great work as always!

    Like

  3. I love this pattern!

    1. I agree with Kostas K. and wish there was a way to include WithEvents variables in this approach, but like him I have not found a way to do it. Anyone?

    2. I use the same name, “type_THIS”, for each module’s private type. Since these types are all private, assigning unique names seems unnecessary.

    3. In MZ-Tools 3.0, I use boilerplate “Module Header” code (below). When starting a new module, I delete the two subs that don’t apply, unpack the commented-out code, and add details as needed.

    4. I automatically include “this.ModuleName” as a variable because my error-handling code (omitted) uses it.

    5. Form modules have Open/Close events; class modules have Initialize/Terminate events; but standard modules do not automatically have anything analogous. Therefore, in order to allow reference to “this.ModuleName” in all three module types, in my standard modules “this()” is written a function so that its value is available as soon as it is needed. I do not have a standard-module mechanism analogous to a Close/Terminate event, but since the private types for standard modules will only contain a “ModuleName” string variable, it seems unnecessary.

    ‘ BEGIN MZ-Tools boilerplate “Module Header” code

    Private Sub CodeTemplateForFormModules()

    ‘Private Type type_THIS
    ‘ ModuleName As String
    ‘ Form As Form_{MODULE_NAME}
    ‘End Type

    ‘Private this As type_THIS
    ‘Private blank As type_THIS

    ‘Private Sub Form_Open(ByRef Cancel As Integer)
    ‘ With this
    ‘ .ModuleName = “{MODULE_NAME}”
    ‘ Set .Form = Me
    ‘ End With
    ‘End Sub

    ‘Private Sub Form_Close()
    ‘ this = blank
    ‘End Sub

    End Sub

    Private Sub CodeTemplateForClassModules()

    ” Configure this class to automatically instantiate a global default instance by doing the following:

    ” 1. Manually export this module to a textfile “ThisClassName.cls”
    ” 2. In a text editor, change the “Attribute VB_PredeclaredId” value from “=False” to “=True”
    ” 3. Re-import the textfile into the Visual Basic Editor, save, and re-compile

    ‘Private Type type_THIS
    ‘ ModuleName As String
    ‘ Parameter As Variant
    ‘End Type

    ‘Private this As type_THIS
    ‘Private blank As type_THIS

    ‘Private Sub Class_Initialize()
    ‘ With this
    ‘ .ModuleName = “{MODULE_NAME}”
    ‘ End With
    ‘End Sub

    ‘Private Sub Class_Terminate()
    ‘ this = blank
    ‘End Sub

    ‘Public Function Create(Parameter As Variant) As {MODULE_NAME}
    ‘ With New {MODULE_NAME}
    ‘ .Initialize Parameter
    ‘ Set Create = .Self
    ‘ End With
    ‘End Function

    ‘Public Sub Initialize(Parameter As Variant) As {MODULE_NAME}
    ‘ With this
    ‘ .Parameter = Parameter
    ‘ End With
    ‘End Sub

    ‘Public Property Get Self() As {MODULE_NAME}
    ‘ Set Self = Me
    ‘End Property

    End Sub

    Private Sub CodeTemplateForStandardModules()

    ‘Private Type type_THIS
    ‘ ModuleName As String
    ‘End Type

    ‘Private Function this() As type_THIS
    ‘ With this
    ‘ If .ModuleName = vbNullString Then
    ‘ .ModuleName = “{MODULE_NAME}”
    ‘ End If
    ‘ End With
    ‘End Function

    End Sub

    ‘ END MZ-Tools boilerplate “Module Header” code

    Like

    1. Thanks for the feedback! I would be careful about standard modules though: if your modules need that level of encapsulation then they should probably be classes. As for the MZ boilerplate, that’s a great idea – maybe someone will PR a similar functionality into Rubberduck at one point =)

      Liked by 1 person

  4. Thanks Mathieu! Regarding the standard modules, my only reason for including them in this pattern was to maintain uniformity in the name-of-the-current-module string consumed by my error-handling code. Other than that, I have no encapsulation-related intentions towards them. I am trying to pare down the amount of code I keep in standard modules down to a bare minimum (user-defined types and a couple of public variables).

    Like

    1. Hey Eric, I’ve been thinking about this, and came to the conclusion that the reason I don’t put something like ModuleName in the private type, is because that field is really a Private Const that belongs to the type/class, rather than the object/instance – to take a C# analogy that would be a “static” field (the keyword means something entirely unrelated in VBA though). As such, having it in a standard module (which are essentially “static classes”), doesn’t agree with the idea of the private type holding the internal state of the instance

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s