Asked by:
Question about classes

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.
Saturday, August 1, 2020 10:08 AM -
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.
- Edited by KareninstructorMVP Saturday, August 1, 2020 10:39 AM
Saturday, August 1, 2020 10:34 AM -
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.
Saturday, August 1, 2020 12:39 PM -
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
- Edited by Cor Ligthert Saturday, August 1, 2020 12:59 PM
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
- Edited by Cor Ligthert Sunday, August 2, 2020 4:00 PM
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
CorSunday, 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 ZhaoMSDN 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.Wednesday, August 12, 2020 5:37 AM