6

I can read a lot over the Internet that VB.Net Modules are the same thing as c#.Net Static Classes. I can also read that something close to a Static Class is a class which would look like this:

'NotInheritable so that no other class can be derived from it
Public NotInheritable Class MyAlmostStaticClass

    'Private Creator so that it cannot be instantiated
    Private Sub New()
    End Sub

    'Shared Members
    Public Shared Function MyStaticFunction() as String
        Return "Something"
    End Function

End Class

I find this code heavy to draft, and to read. I would be much more confortable just using a Module like this:

Public Module MyEquivalentStaticClass
    Public Function MyStaticFunction() as String
        Return "Something"
    End Function
End Module

However, with a Module you loose one level of Namespace hierarchy, and the following 3 statements are equal:

'Call through the Class Name is compulsory
Dim MyVar as String = Global.MyProject.MyAlmostStaticClass.MyStaticFunction()

'Call through the Module Name is OPTIONAL
Dim MyVar as String = Global.MyProject.MyEquivalentStaticClass.MyStaticFunction()
Dim MyVar as String = Global.MyProject.MyStaticFunction()

I find this very inconvenient and this either pollutes the Intelisense, or forces me to create additionnal levels of Namespace, which then means more Module declaration, i.e., more Intelisense pollution.

Is there a workaround or is this the price to pay if you want to avoid the heavy SharedMembers-NotInheritable-PrivateNew Class declaration?

Additionnal references include the very good post by Cody Gray: https://stackoverflow.com/a/39256196/10794555

Ama
  • 1,373
  • 10
  • 24
  • What exactly are you trying to ask? Is it the question posed in the title? Based on the question's body, I infer that you are asking "Is there a way to to prevent the VB [Type Promotion](https://learn.microsoft.com/en-us/dotnet/visual-basic/programming-guide/language-features/declared-elements/type-promotion) feature that allows the equivalent syntax exampled for `MyEquivalentStaticClass.MyStaticFunction`? The title based question seems odd as that one answered by your link to Cody's answer. – TnTinMn Feb 14 '19 at 17:31
  • Both questions in the subject and at the end of my post are tightly related. If Modules and Sharedmembers Classes are identical, then it means there is a workaround to the 'VB Type Promotion' as you named it. – Ama Feb 14 '19 at 23:43

3 Answers3

7

No, there is no exact equivalent to a C# static class in VB.NET. It would be nice if VB had the ability to add the Shared modifier to a class declaration, like this:

Public Shared Class Test  ' This won't work, so don't try it
    ' Compiler only allows shared members in here
End Class

But, unfortunately, it does not. If you do that, the compiler gives you the following error:

Classes cannot be declared 'Shared'

That leaves us with the two options you listed:

  • Either you make a non-instantiable class containing only Shared members (without the safety of that rule being enforced by the compiler), or
  • Use a Module, which makes everything Shared, even though you don't explicitly say so via the Shared modifier

As you said, many people don't like the loss of the class name being required, as a sort-of extra namespace layer, so they prefer the Class with only Shared members over the Module. But, that's a matter of preference.

It's worth noting that, while you don't have to specify the module name everywhere you call its members, you can always do so if you wish:

MyModule.MyMethod()

While a "SharedMembers-NotInheritable-PrivateNew Class", as you so eloquently called it, is the closest approximation to a static class, it's only functionally equivalent. If you use reflection, you'll see that the attributes of the type are not the same. For instance, in VB:

Module MyModule
    Public Sub Main()
        Dim t As Type = GetType(MyClass)
    End Sub
End Module

Public NotInheritable Class MyClass
    Private Sub New()
    End Sub

    Public Shared Sub MyMethod()
    End Sub
End Class

If you take a look at t.Attributes, you'll see that it equals Public Or Sealed. So the MyClass type is both sealed (NotInheritable) and public. However, if you do this in C#:

class Program
{
    static void Main(string[] args)
    {
        Type t = typeof(Test);
    }
}

public static class MyClass
{
    public static void MyMethod()
    { }
}

And you inspect the t.Attributes again, this time, the value is Public | Abstract | Sealed | BeforeFieldInit. That's not the same. Since you can't declare a class in VB as both NotInheritable and MustInherit at the same time, you have no chance of exactly duplicating that thing. So, while they more-or-less are equivalent, the attributes of the types are different. Now, just for fun, let's try this:

Module MyModule
    Public Sub Main()
        Dim t As Type = GetType(MyModule)
    End Sub
End Module

Now, the t.Attributes for the module are Sealed. That's it. Just Sealed. So that's not the same either. The only way to get a true static class in VB (meaning, the type has the same attributes when inspected via reflection) is to write it in a C# class library, and then reference the library in VB.

Steven Doggart
  • 43,358
  • 8
  • 68
  • 105
  • 2
    While I prefer classes with shared methods, there is one case when you must use a module. Extension methods can be declared only within modules (https://learn.microsoft.com/en-us/dotnet/visual-basic/programming-guide/language-features/procedures/extension-methods). You cannot make an extension method from a shared method in a class. – Peter Macej Feb 14 '19 at 17:06
  • 1
    "BeforeFieldInit, attribute, there's no keyword in VB to get the compiler to emit that" - technically correct as there is no single keyword. However, `BeforeFieldInit` will be emitted on a class that contains an inline `Shared` field initializer (i.e. `Shared SomeFieldName As Byte = 0` – TnTinMn Feb 14 '19 at 17:35
  • @TnTinMn Ah! Very interesting. Ok, well, _very_ may be pushing it, but that's good to know :) Thanks. I'll update my answer to make it less misleading. (Actually, I just removed that sentence altogether, since it wasn't needed and wasn't adding much) – Steven Doggart Feb 14 '19 at 17:46
  • That same trick also applies to C# non-Static classes. Other _fun_ is to apply the [StandardModuleAttribute](https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualbasic.compilerservices.standardmoduleattribute?view=netframework-4.7.2) to classes (either VB or C#) in an externally referenced assembly and watch the VB compiler treat all such decorated classes as Modules. – TnTinMn Feb 14 '19 at 18:02
  • If we want to be precise, the NotInheritable and Private New statements are required. However, in practice, is it truly dangerous to avoid including them in a Class which contains only Shared Members? I mean (i) if you inherit a class it means you've looked at what it is made of, and (ii) if you create an instance of a class that also supposes you know what it contains, and even if you didn't then you'd just end up with a class that does nothing.. Does it hurt more, compared to having the luxury of trimming all these Private New and NotInheritable qualifiers? – Ama Feb 15 '19 at 00:00
  • True. It’s not really necessary. It’s just a matter of how similar to a static class you really need it to be. The thing is, in C#, if you make a static class, all members in the class must be static. If not, the compiler will generate an error. That’s a nice safeguard against someone accidentally adding a non-static member to a class which is supposed to only contain static members. In VB, there’s no such safeguard. However, by giving it a private constructor, you’re at least making it so that, if someone does add a non-static, it will be unusable. – Steven Doggart Feb 15 '19 at 00:41
0

I would be much more confortable just using a Module

So use a Module.

Module SomeModuleNameHere

    Public Function MyStaticFunction() As String
        Return "Something"
    End Function

End Module

You don't need Global.MyProject or the Module name at all, just call your function directly, from anywhere:

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Dim x As String = MyStaticFunction()
    Debug.Print(x)
End Sub

But if you want to, you can use the Module name, without the Global part:

Dim x As String = SomeModuleNameHere.MyStaticFunctions

The only time you must use the Module name, however, is if you have two functions with the exact same name in different modules. Then you'd have to differentiate them by using their fully qualified names.

Idle_Mind
  • 38,363
  • 3
  • 29
  • 40
  • 2
    The question includes the fact that the module name is not required but can be provided. I believe OP would rather have the module name required (to avoid "polluting the namespace"). – Blackwood Feb 14 '19 at 13:56
  • @Blackwood, gotcha. Not sure exactly what "polluting the namespace" means. =) – Idle_Mind Feb 14 '19 at 14:39
  • By polluting I mean that when you are at the namespace level of the Module, intelisense shows you both the Module Name and all the public members it contains. (as opposed to showing the members only if I first enter the module name) – Ama Feb 14 '19 at 23:54
  • @ama, Is how to achieve the Intellisense display you described above your true question. Assuming that "if I first enter the module name" should have been "if I first enter the namespace name", that is easy achievable. – TnTinMn Feb 15 '19 at 00:46
  • @TnTinMn I meant to say module, not namespace. However, I am indeed considering having a namespace path where the last layer (which contains the Module) would have the name I would have usually given to the Module (and the Module gets a generic name such as 'Module1'). Then I access the functions through the namespace path I wanted, and never use the module layer. I will post a Self-Reponse to describe this with further details. – Ama Feb 15 '19 at 11:26
0

From all the discussions held so far, and thanks to the input by Steven Doggart and comments by TnTinMn, I have come to conclude with the following broad feedbacks and guidelines.

  • Nota: This post refers to 'Static' Classes, whilst the Static keyword is used for C#.Net, not VB.Net. The VB equivalent is Shared, but Shared Classes are not permited with VB (only the Members). The guidelines described below are tentatives to achieve in VB something close to a C# Static Class. Since such VB Classes cannot be Shared, they are described as 'Static'.
  • Nota bis: In all the examples, I purposely added a layer of Namespace (consistently called "MySpace") so that there is no confusing as to in which Namespace layer the examples sit: they are all in the MySpace layer. The MySpace layer is not compulsory and can be stripped out depending on your needs.

In general

Use a Module but do not rely on the Module name as a Namespace layer. Rather, fully integrate the path in a Namespace declaration, such as:

Namespace MySpace.MyStaticClass
    Module _Module

        Function MyStaticFunction()
            Return "Something"
        End Function

    End Module
End Namespace

Then the Static 'Members' should be accessed via Global.MyProject.MySpace.MyStaticClass.MyStaticFunction()

  • Nota: Part of the Namespace path can be stripped depending on where you are located. Usually, MySpace.MyStaticClass.MyStaticFunction() will be sufficient.
  • Nota bis: Using _Module as the Module name will reduce the appereance of the Module in the Intelisense dropdown, and yet make it crystal clear this is a Module.

When wishing to encaspulate Static Classes

Under such context the general above-mentionned style would produce:

Namespace MySpace.MyStaticClass
    Module _Module

        Function MyStaticFunction()
            Return "Something"
        End Function

    End Module
End Namespace

Namespace MySpace.MyStaticClass.MyStaticSubClass1
    Module _Module

        Function MyStaticFunction()
            Return "Something"
        End Function

    End Module
End Namespace

Namespace MySpace.MyStaticClass.MyStaticSubClass2
    Module _Module

        Function MyStaticFunction()
            Return "Something"
        End Function

    End Module
End Namespace

This can quickly be heavy in the sense that it requires a separate Namespace declaration for each 'encapsulated' 'Static Class'. Disadvantages include:

  1. Heavy review because understanding the Namespace architecture/arborescence will be less intuitive: in the above example that would mean checking all the declaration which include 'MyStaticClass'.
  2. Heavy drafting because of the additionnal Namespace declarations.
  3. Heavy maintenance because changing a parent Namespace will require a change in several Namespace declarations: in the above example that would mean changing 'MyStaticClass' 3 times. (Right-Click/Rename is your best friend here)

An alternative is to use encapsulated Classes with Shared members:

Namespace MySpace
    Public Class MyStaticClass

        Public Function MyStaticFunction()
            Return "Something"
        End Function

        Public Class MyStaticSubClass1

            Public Shared Function MyStaticFunction()
                Return "Something"
            End Function

        End Class

        Public Class MyStaticSubClass2

            Public Shared Function MyStaticFunction()
                Return "Something"
            End Function

        End Class

    End Class
End Namespace
  • Nota: As Steven Doggart pointed out in a separate post, people usually import Namespaces, but do not import Classes, so encapsulating Classes will usually "force" the reliance on the full path across encapsulated Classes : MyStaticClass.MyStaticSubClass1.

You cannot encapsulate a Module within another Module, but you could always use a mixture of a Module in which you encapsulate one or several Classes and Sub-Classes. The example below achieves something similar to the above example:

Namespace MyStaticClass
    Public Module _Module

        Public Function MyStaticFunction()
            Return "Something"
        End Function

        Public Class MyStaticSubClass1

            Public Shared Function MyStaticFunction()
                Return "Something"
            End Function

        End Class

        Public Class MyStaticSubClass2

            Public Shared Function MyStaticFunction()
                Return "Something"
            End Function

        End Class

    End Module
End Namespace

When publishing a Class Library (DLL)

If your final product is a DLL you intend to share with a broader audience, it is recommended to put safety nets around your 'Static' Classes. Although this will not affect how the compiler will see your code, it will prevent someone else from making mistakes, or at least quickly trigger errors and assist debugging swiftly:

  1. Make the Class NotInheritable, so that no one tries to derive a Class from a Static Class: it is typically useless to derive such Classes.
  2. Make the New Creator statement Private, so that no one tries to instantiate the Class: the Static Class should not include any non-Static (Shared) members; if so, that is a typo and trying to instantiate the non-Shared Member will likely bring problems.

The example below achieves something similar to the above examples:

Namespace MySpace
    Public NotInheritable Class MyStaticClass

        Private Sub New()
        End Sub

        Public Function MyStaticFunction()
            Return "Something"
        End Function

        Public NotInheritable Class MyStaticSubClass1

            Private Sub New()
            End Sub

            Public Shared Function MyStaticFunction()
                Return "Something"
            End Function

        End Class

        Public NotInheritable Class MyStaticSubClass2

            Private Sub New()
            End Sub

            Public Shared Function MyStaticFunction()
                Return "Something"
            End Function

        End Class

    End Class
End Namespace

When dealing with an Extension

A <System.Runtime.CompilerServices.Extension()> can only be declared within a Module block. However the Module Name has no impact on the Extension so this topic is not really relevant here.

See link provided by Peter Macej: https://learn.microsoft.com/en-us/dotnet/visual-basic/programming-guide/language-features/procedures/extension-methods

Ama
  • 1,373
  • 10
  • 24
  • According to the Visual Basic Type Promotion documentation, you can [defeat type promotion](https://learn.microsoft.com/en-us/dotnet/visual-basic/programming-guide/language-features/declared-elements/type-promotion#defeat-of-type-promotion) by having something else at the module level named the same as the items inside the module. I wonder if that means you could use Private Constants for that. It may, however, require too much extra code to stop that. – Shaggie Jun 23 '23 at 19:13