none
Question about classes RRS feed

  • Question

  • I'm a bit embarrassed to be asking this because I ought to be able to get an answer off the internet.
    I'm writing a windows desktop app that uses a lot of different calculations that I have just been putting in a module:

    Public Class Form1
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            Dim dblHelixDegrees As Double
            Dim dblDiameter As Double = CDbl(txtDia.Text)
            Dim dblPitch As Double = CDbl(txtPitch.Text)
    
            dblHelixDegrees = GetHelixAngle(dblDiameter, dblPitch)
    
            txtHelix.Text = CStr(dblHelixDegrees)
        End Sub
    End Class
    
    Imports System.Math
    Module modCalcs
        Public Function GetHelixAngle(dblDia As Double, dblPitch As Double) As Double
    
            Dim dblCircumference As Double
            Dim dblHelixRadians As Double
            Dim dblHelixDegrees As Double
    
            dblCircumference = PI * dblDia
            dblHelixRadians = Atan(dblPitch / dblCircumference) 'helix angel radians
            dblHelixDegrees = dblHelixRadians * (180 / PI)
    
            Return dblHelixDegrees
        End Function
    End Module
    This one is straightforward requiring just two variables. 
    Anyway, I thought maybe I ought to learn how to use classes to make the code more readable and up to date.
    But when I try to search about classes I only seem to find examples of "Employee" or "Car" or "Cat" Classes.
    Anyone willing to give me a guidance on converting the above module as a class? 
    Or maybe a class is not the right thing for this instance?


    Saturday, August 1, 2020 9:02 AM

All replies

  • Hello,

    There is a bite to learn about class which you can learn over time. Your code module is easy to convert to a class. The reason why your presented code is easy is that there is no initialization needed. What you get out of what is presented below is encapsulation, nothing is exposed to the world like with a code modules.

    The key is "shared" which means you can call GetHelixAngle directly.

    Imports System.Math
    Public Class Calculations
        Public Shared Function GetHelixAngle(dblDia As Double, dblPitch As Double) As Double
    
            Dim dblCircumference As Double
            Dim dblHelixRadians As Double
            Dim dblHelixDegrees As Double
    
            dblCircumference = PI * dblDia
            dblHelixRadians = Atan(dblPitch / dblCircumference) 'helix angel radians
            dblHelixDegrees = dblHelixRadians * (180 / PI)
    
            Return dblHelixDegrees
        End Function
    End Class
    

    Examine the usage

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim dblHelixDegrees As Double
        Dim dblDiameter As Double = CDbl(txtDia.Text)
        Dim dblPitch As Double = CDbl(txtPitch.Text)
    
        dblHelixDegrees = Calculations.GetHelixAngle(dblDiameter, dblPitch)
    
        txtHelix.Text = CStr(dblHelixDegrees)
    End Sub

    Here is something else to consider, classes offer lightweight containers e.g. many developers when working with databases will use a DataSet/DataTable which can be good or bad depending on the task at hand.

    When should a code module be used? Only for language extension methods and to describe say delegates or to declare API signatures.

     


    Please remember to mark the replies as answers if they help and unmarked them if they provide no help, this will help others who are looking for solutions to the same or similar problem. Contact via my Twitter (Karen Payne) or Facebook (Karen Payne) via my MSDN profile but will not answer coding question on either.

    NuGet BaseConnectionLibrary for database connections.

    StackOverFlow
    profile for Karen Payne on Stack Exchange

    Saturday, August 1, 2020 10:08 AM
    Moderator
  • Now let's go deeper. The following code comes from one of my GitHub repositories. The examples uses Microsoft Entity Framework but doesn't have to be, could be. I just want you to see a more complex examples.

    This is a partial class meaning I can create another class from it which exposes what's in this class.

    Call this the original class which resides in a folder named Models. Also, the annotations on the properties can be used for validation (not getting into that here, see this code and this code)

    Partial Public Class Customer
    
        Public Sub New()
            Orders = New HashSet(Of Order)()
        End Sub
    
        <Key>
        Public Property CustomerIdentifier As Integer
        <Required>
        <StringLength(40)>
        Public Property CompanyName As String
        '<StringLength(30)>
        'Public Property ContactName As String
        Public Property ContactIdentifier As Integer?
        Public Property ContactTypeIdentifier As Integer?
        <StringLength(60)>
        Public Property Street As String
        <StringLength(15)>
        Public Property City As String
        <StringLength(10)>
        Public Property PostalCode As String
        Public Property CountryIdentifier As Integer?
        <StringLength(24)>
        Public Property Phone As String
        <Column(TypeName:="datetime2")>
        Public Property ModifiedDate As Date?
        Public Overridable Property Contact As Contact
        Public Overridable Property ContactType As ContactType
        Public Overridable Property Country As Country
        Public Overridable Property Orders As ICollection(Of Order)
    
    End Class

    Now in the same project in a folder name classes I do what's called a projection which to keep it simple provides a property to read only data I want, not everything in the class above.

    Imports System.ComponentModel.DataAnnotations.Schema
    Imports System.Linq.Expressions
    
    Partial Public Class Customer
        <NotMapped>
        Public Property FirstName As String
        <NotMapped>
        Public Property LastName As String
        Public Shared ReadOnly Property Projection() As Expression(Of Func(Of Customer, CustomerEntity))
    
            Get
                Return Function(customer) New CustomerEntity() With {
                    .CustomerIdentifier = customer.CustomerIdentifier,
                    .CompanyName = customer.CompanyName,
                    .Street = customer.Street,
                    .City = customer.City,
                    .PostalCode = customer.PostalCode,
                    .ContactTypeIdentifier = customer.ContactTypeIdentifier.Value,
                    .ContactTitle = customer.ContactType.ContactTitle,
                    .CountryName = customer.Country.CountryName,
                    .FirstName = customer.Contact.FirstName,
                    .LastName = customer.Contact.LastName,
                    .ContactIdentifier = CInt(customer.ContactIdentifier),
                    .CountryIdentifier = customer.CountryIdentifier}
            End Get
    
        End Property

    Then in a "worker" class use Projection

    Public Async Function AllCustomersAsync() As Task(Of List(Of CustomerEntity))
    
    	Return Await Task.Run(
    		Async Function()
    			Dim customerItemsList As List(Of CustomerEntity) =
    					Await Context.Customers.Select(Customer.Projection).ToListAsync()
    			Return customerItemsList.OrderBy(Function(customer) customer.CompanyName).ToList()
    		End Function)
    
    End Function

    Well, what is CustomerEntity above? CustomerEntity is getting into the thick of it. CustomerEntity implements INotifyPropertyChanged interface which when implemented in a form with a BindingList or BindingSource allows changes to data bound to say TextBox controls to be immediately updated without user intervention.

    NOTE 1: What is BaseEntity? In this case when a class inherits it that class must implement it. In this case BaseEntity indicates the class using it must have these properties.

    Public Class BaseEntity
        Public Property CreatedAt() As Date?
        Public Property CreatedBy() As String
        Public Property LastUpdated() As Date?
        Public Property LastUser() As String
        Public Property IsDeleted As Boolean?
    End Class
    So if we want that also say for a Contact class we know both classes have those properties (and this for your level is going deeper but worth you exploring). Now in some event that can be used by both Contact and CustomerEntity we want to know about properties implementing by BaseEntity we can cast said object to BaseEntity to examine or change properties coming from BaseEntity.

    CType(currentEntry.Entity, BaseEntity).IsDeleted = True


    NOTE 2: Looks like a lot to code and why should I even bother? Well here is a secret, there are tools (that are paid for) that can automate much of what is done below e.g. Resharper which once you use it there is no going back. I have it installed on two computers, work and home.

    Public Class CustomerEntity
        Inherits BaseEntity
        Implements INotifyPropertyChanged
    
        Private _customerIdentifier1 As Integer
        Private _companyName1 As String
        Private _contactIdentifier1 As Integer?
        Private _firstName1 As String
        Private _lastName1 As String
        Private _contactTypeIdentifier1 As Integer
        Private _contactTitle1 As String
        Private _address1 As String
        Private _city1 As String
        Private _postalCode1 As String
        Private _countryIdentifier1 As Integer?
        Private _countyName1 As String
    
        Public Property CustomerIdentifier() As Integer
            Get
                Return _customerIdentifier1
            End Get
            Set
                _customerIdentifier1 = Value
                OnPropertyChanged()
            End Set
        End Property
        <Required>
        Public Property CompanyName() As String
            Get
                Return _companyName1
            End Get
            Set
                _companyName1 = Value
                OnPropertyChanged()
            End Set
        End Property
    
        Public Property ContactIdentifier() As Integer?
            Get
                Return _contactIdentifier1
            End Get
            Set
                _contactIdentifier1 = Value
                OnPropertyChanged()
            End Set
        End Property
    
        Public Property FirstName() As String
            Get
                Return _firstName1
            End Get
            Set
                _firstName1 = Value
                OnPropertyChanged()
            End Set
        End Property
    
        Public Property LastName() As String
            Get
                Return _lastName1
            End Get
            Set
                _lastName1 = Value
                OnPropertyChanged()
            End Set
        End Property
    
        Public ReadOnly Property ContactName() As String
            Get
                Return $"{FirstName} {LastName}"
            End Get
        End Property
    
        Public Property ContactTypeIdentifier() As Integer
            Get
                Return _contactTypeIdentifier1
            End Get
            Set
                _contactTypeIdentifier1 = Value
                OnPropertyChanged()
            End Set
        End Property
    
        Public Property ContactTitle() As String
            Get
                Return _contactTitle1
            End Get
            Set
                _contactTitle1 = Value
                OnPropertyChanged()
            End Set
        End Property
    
        Public Property Street() As String
            Get
                Return _address1
            End Get
            Set
                _address1 = Value
                OnPropertyChanged()
            End Set
        End Property
    
        Public Property City() As String
            Get
                Return _city1
            End Get
            Set
                _city1 = Value
                OnPropertyChanged()
            End Set
        End Property
    
        Public Property PostalCode() As String
            Get
                Return _postalCode1
            End Get
            Set
                _postalCode1 = Value
                OnPropertyChanged()
            End Set
        End Property
    
        Public Property CountryIdentifier() As Integer?
            Get
                Return _countryIdentifier1
            End Get
            Set
                _countryIdentifier1 = Value
                OnPropertyChanged()
            End Set
        End Property
    
        Public Property CountryName() As String
            Get
                Return _countyName1
            End Get
            Set
                _countyName1 = Value
                OnPropertyChanged()
            End Set
        End Property
    
        Public Event PropertyChanged As PropertyChangedEventHandler _
            Implements INotifyPropertyChanged.PropertyChanged
    
        Protected Overridable Sub OnPropertyChanged(
            <CallerMemberName> Optional memberName As String = Nothing)
    
            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(memberName))
    
        End Sub
    
    End Class

    All code above comes from https://github.com/karenpayneoregon/ef-track-added-modified-vb

    Hopefully the above over time is helpful.

    EDIT:

    Search the web for "gang of 4" design patterns. One cool one is the Builder pattern where I have some code samples for it here.

    https://github.com/karenpayneoregon/FluentPatternVisualBasic


    Please remember to mark the replies as answers if they help and unmarked them if they provide no help, this will help others who are looking for solutions to the same or similar problem. Contact via my Twitter (Karen Payne) or Facebook (Karen Payne) via my MSDN profile but will not answer coding question on either.

    NuGet BaseConnectionLibrary for database connections.

    StackOverFlow
    profile for Karen Payne on Stack Exchange


    Saturday, August 1, 2020 10:34 AM
    Moderator
  • Thanks Karen, 
    Your second post is going to take some work...

    In the meantime.

    Your first post behaves basically like an ordinary function.

    I found something else which may make the code more manageable.

    Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
          
            Dim getHelix As New clsMaths
           
            getHelix.Diameter = CDbl(txtDia.Text)
            getHelix.Pitch = CDbl(txtPitch.Text)
            txtHelixFromClass.Text = CStr(getHelix.Helix)
    
        End Sub
    
    
    
    Public Class clsMaths
        
        Dim dblCircumference As Double
        Dim dblHelixRadians As Double
        Public dblHelixDegrees As Double
        Dim dblDiameter As Double
        Dim dblPitch As Double
    
        Public Property Diameter As Double
            Set(value As Double)
                dblDiameter = value
            End Set
            Get
    
            End Get
    
        End Property
    
        Public Property Pitch As Double
            Set(value As Double)
                dblPitch = value
            End Set
            Get
    
            End Get
        End Property
    
        Public Property Helix() As Double
            Set(value As Double)
    
            End Set
            Get
                dblCircumference = PI * dblDiameter
                dblHelixRadians = Atan(dblPitch / dblCircumference) 'helix angel radians
                dblHelixDegrees = dblHelixRadians * (180 / PI)
                Helix = dblHelixDegrees
            End Get
    
        End Property
       
    
    End Class
    Don't know if this is a good idea or not.
    I don't know what to put in the 'Get' sections of the Diameter & Pitch properties
    I suppose if I need to check these values I need a way to return them.

    Saturday, August 1, 2020 11:09 AM
  • What I presented in the first reply is a standard, about eight months ago I had a joint conversation with one of the project managers at Microsoft and the head person for code documentation at Microsoft for me to write code samples for Microsoft docs web site. In this phone chat they both stressed that using shared properties and methods is there standard unless needed and I agreed as I've been doing so for years now.

    If there is no prior initialization needed use a shared method as I did. Most developers would do the following instead.

    Public Class Calculations
        Public Function GetHelixAngle(dblDia As Double, dblPitch As Double) As Double
    
            Dim dblCircumference As Double
            Dim dblHelixRadians As Double
            Dim dblHelixDegrees As Double
    
            dblCircumference = PI * dblDia
            dblHelixRadians = Atan(dblPitch / dblCircumference) 'helix angel radians
            dblHelixDegrees = dblHelixRadians * (180 / PI)
    
            Return dblHelixDegrees
        End Function
    End Class

    Then this

    Dim calcs = New Calculations
    dblHelixDegrees = calcs.GetHelixAngle(dblDiameter, dblPitch)

    There is zero reasons for this as there is nothing needed to prepare before calling the method. Now with your current suggestion I would do this (and note you should never prefix a class with 'cls')

    Public Class Maths
        Public Property Diameter As Double
        Public Property Pitch As Double
        Public ReadOnly Property Helix() As Double
            Get
                Dim dblCircumference = PI * Diameter
                Dim dblHelixRadians = Atan(Pitch / dblCircumference) 'helix angel radians
                Dim dblHelixDegrees = dblHelixRadians * (180 / PI)
                Helix = dblHelixDegrees
            End Get
        End Property
    End Class
    

    Or we can do

    Public Class Maths
        Public Shared Property Diameter As Double
        Public Shared Property Pitch As Double
        Public Shared ReadOnly Property Helix() As Double
            Get
                Dim dblCircumference = PI * Diameter
                Dim dblHelixRadians = Atan(Pitch / dblCircumference) 'helix angel radians
                Dim dblHelixDegrees = dblHelixRadians * (180 / PI)
                Helix = dblHelixDegrees
            End Get
        End Property
    End Class
    

    Then when dealing with shared we can create a class such as the one below.

    Option Infer On
    ''' <summary>
    ''' Thread safe singleton responsible for creating
    ''' unique sequence
    ''' </summary>
    Public NotInheritable Class ReferenceIncrementer
        Private Shared ReadOnly Lazy As New Lazy(Of ReferenceIncrementer)(Function() New ReferenceIncrementer())
    
        Public Shared ReadOnly Property Instance() As ReferenceIncrementer
            Get
                Return Lazy.Value
            End Get
        End Property
    
        Private _baseList As New List(Of Integer)()
        Private Sub CreateList()
            _baseList = New List(Of Integer)()
    
            For index = 1 To 8999
                _baseList.Add(index)
            Next index
        End Sub
        Public Function GetReferenceValue() As String
            If Not _baseList.Any() Then
                CreateList()
            End If
    
            Dim number = _baseList.FirstOrDefault()
            _baseList.Remove(number)
    
            Return $" REF: {number:D4}"
    
        End Function
    
        ''' <summary>
        ''' Instantiate List
        ''' </summary>
        Private Sub New()
            CreateList()
        End Sub
        ''' <summary>
        ''' Used to reset at a given time e.g. right before midnight,
        ''' perhaps by a scheduled job.
        ''' </summary>
        Public Sub Reset()
            CreateList()
        End Sub
    End Class
    
    

    And call it anywhere in the project.

    Console.WriteLine(ReferenceIncrementer.Instance.GetReferenceValue())

    Notes

    • Nothing is exposed to compromise anything
    • Since the class is marked as NotInheritable it can not be inherited and abused
    • This is known as a Singleton 
    • It's thread safe.

    In closing, I've been using classes in small projects to large projects with code reviews and peer reviews (our teams do this to each other and we all have 20 plus years of doing this) were everyone knows standards in and outside the team and go with Microsoft best practices.



    Please remember to mark the replies as answers if they help and unmarked them if they provide no help, this will help others who are looking for solutions to the same or similar problem. Contact via my Twitter (Karen Payne) or Facebook (Karen Payne) via my MSDN profile but will not answer coding question on either.

    NuGet BaseConnectionLibrary for database connections.

    StackOverFlow
    profile for Karen Payne on Stack Exchange

    Saturday, August 1, 2020 12:39 PM
    Moderator
  • Andy,

    On Internet you find more nonsense about classes then sense. 

    The word class is popular and therefore often misused. In real life it mean the same like a school class, but not the object, let say the grade (but has international more meanings as school grade is typical USA (but not only there) and it does not really fit exact). 

    You saw already the word object. An object is something retainable (it exist)

    A class in many program languages is misused and is nothing more than a module. It is direct created as an object (static they tell).

    That was also the case with VB6, therefore a Form is a strange thing in VB.Net (and only the form). If you use it in the designer it becomes direct an instance (object).

    Otherwise a class is only a kind of template with which you can make objects (instances) another word in .Net is Type. With a class can you make more objects with the same class. Let say you have a class Spoke, that you can use them in any Wheel you create.

    The advantage you ask yourself, beside the later, in .Net it goes out of scope (is released) as soon as it is not used (not referenced and has itself no references anymore) in your program. 

    The garbage collector takes than care of really releasing the memory. 

    A module uses the memory at all the runtime. 

    Be aware instancing an object (New) takes very few time and absolute less than setting values to zero in a module.


    Success
    Cor




    Saturday, August 1, 2020 12:54 PM
  • Thanks Karen, Cor.

    I guess the reason I'm thinking writing a class might help managing this is that sometimes I want to return more than one value. There are loads of these calculations, It's hard to follow.
    For instance I might want access to the circumference and helix angle in radians as well as the helix angle in degrees. Currently with the module and function I'd have to stick the values into global variables to get access to them or write another function for each one.
    So I'm guessing that I'd make some more public properties for helixRadian & Circumference.

    I can't actually get to try this at the mo, 

    But I'll get at it tomorrow or Monday.

    Saturday, August 1, 2020 7:02 PM
  • Thanks Karen, Cor.

    I guess the reason I'm thinking writing a class might help managing this is that sometimes I want to return more than one value. There are loads of these calculations, It's hard to follow.
    For instance I might want access to the circumference and helix angle in radians as well as the helix angle in degrees. Currently with the module and function I'd have to stick the values into global variables to get access to them or write another function for each one.
    So I'm guessing that I'd make some more public properties for helixRadian & Circumference.

    I can't actually get to try this at the mo, 

    But I'll get at it tomorrow or Monday.

    Andy,

    That is exactly where you not should use it. If you want to make a class that returns values, then let it be a function. Why should all be returned at once. Mostly takes it only more processing. 

    You should have a look at overloading, that is the way to go in this. For instance the Bitmap class is an endless overloaded class. Therefore that strange use of int16 and int32 if you do not know the reason for it. It is because a signature is distillated from the given parameters where the types are important. 

    https://docs.microsoft.com/en-us/dotnet/api/system.drawing.bitmap.-ctor?view=dotnet-plat-ext-3.1

    But as well the Font class

    https://docs.microsoft.com/en-us/dotnet/api/system.drawing.font?view=dotnet-plat-ext-3.1

    I made a sample for you. Be aware this is only to show how it goes and not something you should copy and paste or it would be to try it with debug

    Module Program
        Sub Main()
            Dim B As Int32 = 10
            Dim C As Int16 = 10
            Dim AndysMath As New Math
            Console.WriteLine(AndysMath.Calc(B))
            Console.WriteLine(AndysMath.Calc(C))
            Console.ReadLine()
        End Sub
    End Module
    Public Class Math
    
        ''' <summary>
        ''' Returns * 10
        ''' </summary>
        ''' <param name="A = Int32"></param>
        ''' <returns>integer</returns>
        Overloads Function Calc(A As Int32) As Integer
            Return A * 10
        End Function
        ''' <summary>
        ''' Returns * 20
        ''' </summary>
        ''' <param name="A = Int16"></param>
        ''' <returns></returns>
        Overloads Function Calc(A As Int16) As Integer
            Return A * 20
        End Function
    End Class

     


    Success
    Cor



    Sunday, August 2, 2020 3:57 PM
  • Thanks Cor,
    Maybe I should explain what the actual requirement is.
    It's about screw threads.
    Screws have a number of properties:
    Thread type eg Metric coarse
    Nominal diameter
    Pitch
    Pitch diameter (Effective diameter).
    Minor diameter.
    Root radius
    Nominal allowance
    Nominal tolerance
    Pitch diameter allowance
    Pitch tolerance
    Height
    etc.
    However there are only three of these properties that need to be known to fully specify the thread.
    Thread type, Nominal diameter & Pitch. All of the others are some function of either the pitch or the diameter or the pitch and the diameter.
     Eg. Root radius = 0.107 * P, Pd = NomDia - 0.595875p
    I was thinking that creating a thread object might be an ideal way to make these property values available to the rest of the program.
    Then textbox1.text = Cstr(object.rootRadius)

    Sunday, August 2, 2020 6:25 PM
  • I assume what your looking for is the Class Constructor. In VB that is the Sub New. (Be aware that .Net has no deconstructers as that is all done by managed code). 

    Module Program
        Sub Main()
            Dim TheObject As New Andy(2, 3)
            Console.WriteLine(TheObject.AResult)
            Console.ReadLine()
        End Sub
    End Module
    Public Class Andy
        Private WhateverA As Integer
        Private WhateverB As Integer
        Public ReadOnly Property AResult As Integer
            Get
                Return WhateverA * WhateverB
            End Get
        End Property
        Public Sub New(Aa As Integer, Ab As Integer)
            WhateverA = Aa
            WhateverB = Ab
        End Sub
    End Class


    Success
    Cor

    Sunday, August 2, 2020 7:08 PM
  • Hi AndyNakamura,

    How is the question going? If your question has been answered then please click the "Mark as Answer" Link at the bottom of the correct post(s), so that it will help other members to find the solution quickly if they face a similar issue.

    Best Regards,

    Xingyu Zhao


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    19 hours 54 minutes ago
    Moderator