Improve class design – there is a function that should not be callable - vb.net

Today, I thought to myself that I would quickly create a descriptive example of the access modifiers Public, Private and Protected for someone, taken from real life.
The following example: A caretaker can spend a budget provided by the landlord himself without having to call for every little thing. For moderate repairs, however, he must call the landlord or the landlord's son and ask for it. The son then knows what money he may spend. Big repairs can only be decided by the landlord (not the son).
I transferred that to source code.
I want to improve the class design, because I can still write Tom.DecideMajorRepair (" ") in Form1.vb, which should not work and what I find unclean.
Form1.vb
Public NotInheritable Class FormMain
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim Hans As New ClassVermieter ' Landlord
Dim Tom As New ClassVermietersSohn ' Landlord's son
'easily accessible
Dim MyBudget As UInt16 = Hans.Caretakers_budget_for_minor_repairs - 200US
Dim Budget_received_1 As UInt16 = Hans.DecideModerateRepair("Unfortunately, the heating is broken!")
' Call son because father didn't answer the phone (or whatever). He can also decide that (protected).
' I still have to ask permission from one of them.
Dim Budget_received_2 As UInt16 = Tom.DecideForHimselfModerateRepair("Unfortunately, the heating is broken!")
' Son cannot decide a major repair - only the landlord.
Dim Budget_received_3 As UInt16 = Tom.DecideForHimselfLargeRepair("Unfortunately, the heating is broken!")
Dim Budget_received_4 As UInt16 = Hans.DecideMajorRepair("Unfortunately, the heating is broken!")
End Sub
End Class
ClassVermieter.vb (landlord)
Public Class ClassVermieter
''' <summary>
''' per month
''' </summary>
Public Property Caretakers_budget_for_minor_repairs As UInt16 = 500US
'–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
Protected Property Permission_for_medium_repairs As UShort
'–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
Private Permission_for_large_repairs As UInt16
'answers the phone call from the caretaker
Public Function DecideModerateRepair(ByVal Message_from_the_caretaker As String) As UInt16
Permission_for_medium_repairs = 1000US
Return Permission_for_medium_repairs
End Function
'answers the phone call from the caretaker
Public Function DecideMajorRepair(ByVal Message_from_the_caretaker As String) As UInt16
Permission_for_large_repairs = 5000US
Return Permission_for_large_repairs
End Function
End Class
ClassVermietersSohn.vb (Landlord's son)
Public Class ClassVermietersSohn : Inherits ClassVermieter
Private ReadOnly zero As UInt16 = 0US
'answers the phone call from the caretaker
'He can decide for himself whether a moderate repair should be carried out.
Public Function DecideForHimselfModerateRepair(ByVal Message_from_the_caretaker As String) As UInt16
Permission_for_medium_repairs = 1000US
Return Permission_for_medium_repairs
End Function
'answers the phone call from the caretaker
Public Function DecideForHimselfLargeRepair(ByVal Message_from_the_caretaker As String) As UInt16
Return zero 'Well, that was nothing, because the son cannot spend (not see) the large amounts!
End Function
End Class
This code is kept elementarily. I am aware that money is not handled with Uint16. The first thing I wanted to do was build the structure.
The difficulty that kept me from solving it myself was that I didn't know how to change the Get and Set without an error message.

In terms of the domain, I would not put the burden of knowing "whom" to ask on the caretaker. Ideally, the caretaker should have a single point of contact which can internally route the request and return true/false.
One design decision is how to parameterise the repair request.
This is most easily done by using an enum RepairTypes with a list of possible repair requests.
Then, I would have an interface "RepairApprover" with a method "boolean request(RepairType rt) throws NotSupportedException" - the exception would be used to "go up the chain"
Next, I would either have a LandlordAssistant class implement RepairApprover,
and a separate Landlord class also implementing RepairApprover.
Internally, the Landlord class would have a private field for LandlordAssistant so that it could reuse the code for minor and medium repairs.
I would then make a "ApprovalHierarchy" class with method "RepairApproval getMyManager()"
I would inject the LandlordAssistant as a "RepairApprover" instance into the Caretaker class constructor.
Internally, the LandlordAssistant.request method would try to respond to a small or medium request. If the request type is MAJOR, it would call ApprovalHierarchy.getMyManager(this).request(..) thus passing the request up the chain.
Hope that helps?

Related

So a VB interface can't have shared functions. Is there an alternative to creating dummy objects?

To avoid getting into the weeds on my particular program, let me just create a simplified case.
I have a generic class that should work on a variety of objects. Each of those objects must implement a certain interface.
What I WANT to say is something like:
Public Interface GenThing
Shared Function thing_name() As String ' This doesn't work! Can't be shared!
Sub FillOne(row As DataRow)
End Interface
public class Thing1
implements GenThing
public shared function thing_name() as string implements GenThing.thing_name
return "thing number one"
end function
public sub FillOne(row as DataRow) implements GenThing.MakeOne
... bunch of work ...
end sub
end class
public class ThingUtil(of T as {GenThing,New})
public function GetList(id as integer) as List(of T)
dim name=T.thing_name() ' This doesn't work!
dim ds as DataSet=GetData(name,id) ' bunch of work here that's the whole point of the class but not relevant to the question
dim my_list = new List(of T)
for each row as DataRow in ds.tables(0).rows
dim my_t = new T()
my_t.FillOne(row)
my_list.add(my_t)
next
return my_list
end function
end class
Do you get my problem? I need every class that implements the interface to have a function that returns a "name" that is used to get the data that is needed to create an instance of the object. But I need to know this name BEFORE I create the instance, because I need it to be able to create the instance. But VB doesn't allow an interface to have a shared function, so what I want to write doesn't work.
So what I've done is this:
I make thing_name not shared.
Then instead of simply "dim name=T.thing_name()", I write
dim dummy = new T()
dim name = dummy.thing_name()
Okay, it works, but it seems really ugly. I create an instance of the object, with all the overhead that that involves, just to get a piece of constant text.
Is there a better way? Or am I making a big deal out of nothing?
Update
I see that two people voted to close this question on the grounds that it is the same as "Why can't we have shared functions in an interface?"
I am not asking why I can't have a shared. I am saying, GIVEN that I can't, how do I solve this particular problem?
There's no really simple way of fixing this, no.
Depending on what thing_name does, however, you might approach things in a different way. If each implementation just returns a constant value, then it's effectively metadata about the class - and could be described in an attribute instead, which can be fetched at execution time. (See Type.GetCustomAttributes.) Unfortunately you can't then enforce all types implementing the interface to be decorated with the attribute - but you could write a unit test to check this pretty easily.
If thing_name needs to really do work at execution time, that's tougher. You could potentially look for a well-known shared method name instead and execute that via reflection (and again have unit tests to check that it's implemented properly).
I realize this is from a few years ago, but running into a similar problem, I wanted to offer a different solution. Pass a delegate as parameter to the ThingUtil constructor. You avoid having to put a shared method in an interface, and the constructor will force you to include the parameter at compile time.
You can add more delegates if needed, or to make it even simpler in this case, just pass name as a string instead of get_name as a delegate.
Define the delegate in the interface:
Public Interface GenThing
Delegate Function ThingNameDelegate() As String
Sub FillOne(row As DataRow)
End Interface
Public Class Thing1
Implements GenThing
Public Shared Function thing_name() As String 'name this whatever you want
Return "thing number one"
End Function
Public Sub FillOne(row As DataRow) Implements GenThing.FillOne
'do stuff
End Sub
End Class
In ThingUtil, add a member to store the delegate, a constructor parameter to to accept, and call it with .Invoke():
Public Class ThingUtil(Of T As {GenThing, New})
Private m_thing_name As GenThing.ThingNameDelegate
Public Sub New(thing_name As GenThing.ThingNameDelegate)
m_thing_name = thing_name
End Sub
Public Function GetList(id As Integer) As List(Of T)
Dim name = m_thing_name.Invoke()
Dim ds As DataSet = GetData(name, id) ' bunch of work here that's the whole point of the class but not relevant to the question
Dim my_list = New List(Of T)
For Each row As DataRow In ds.Tables(0).Rows
Dim my_t = New T()
my_t.FillOne(row)
my_list.Add(my_t)
Next
Return my_list
End Function
End Class
Finally, use it like this:
Dim tu as new ThingUtil(Of Thing1)(AddressOf Thing1.get_name)
tu.GetList(1)

Pass user input to new instance of class

Alright, I have looked through 20 pages on here and can't find what I'm looking for... I've seen it in C# and other languages.. but not Visual Basic..
Say I have a class:
Public Class Cars
Private doors as integer
Private Liters as double
Private otherStuff as string
' more code'
end class
Say I also have a Form.. an inputForm we'll call it that has numerous textboxes for users to input these characteristics. The first textbox is labeled nameTextBox. Is there any way to assign the string value of that textbox as a new car?
something to the likes of..
dim nameTextBox.value as new car
??
The fields in your class are private, so they arent of much use - no other code will be able to see those values.
Public Class Car
Public Property Make As String
Public Property Model As String
Public Property Year As Integer
' something the class may use/need but doesnt expose
Private DealerCost As Decimal
' the constructor - called when you create a NEW car
Public Sub New(mk As String, md As String)
Make = mk
Model = md
End Sub
...
End Class
By specifying only a constructor which takes params, I am saying that you cannot create a new car without specifying those properties. If the constructor takes no params, then you can create an "empty" car object and set each property individually. You can do both - called overloading - so you can create a car with or without the Make and Model at the outset.
As Public Properties, other code can examine them to see what kind of car this is.
Dim myCar = New Car("Toyata", "Corolla")
myCar.Year = 2013
myCar.Color = Color.Blue
The text used of course can come from user input:
Dim myCar = New Car(tbMake.Text, tbModel.Text)
Dim yr As Int32
If Integer.TryParse(tbYear.Text, yr) Then
myCar.Year = yr
Else
' ToDo: scold user
End If

VB trouble instantiating object when constructor has a structure data type

I really like the community. I am trying to teach myself vb by trying out what I have learned. I am learning how to use enum's, structures and objects so I thought I would mix them all together ti get a better understanding of whats going on. I am using a Card as an example (been practicing everything with cards)
I created an enum called Suit
Public Enum suit As Integer
Spades = 0
Diamonds =1
Clovers = 2
Hearts = 3
End Enum
Next I created structure that uses the enum data type I just created
Public structure cardValue
Public cardSuit As suit <--- enum
Public cardName As Integer
End Structure
Lastely I created a class of a card
Public Class Card
Private actualCard As cardValue
Get
Return actualCard
End Get
Set(value As cardValue)
End Set
actualCard=value
End Property
Sub New (actualCard As cardValue)
Me.actualCard = actualCard
End Sub
End Class
Now here is my problem
When I goto create a object of the class Card I am not sure how to pass the arguments into the constructor?
Dim myCard As Card = New Card ( I want Spade , and a card value of 10 )
not sure how to pass it a Value, everything I try I get errors of not valid type
I am also not sure how to make the code grey, I am very sorry about that.
With your actual constructor you could do :
Dim c As New Card(New cardValue With {.cardSuit = suit.Spades, .cardName = 10})
But the easiest way would be to change your constructor :
Public Sub New(suitValue As suit, cardValue As Integer)
ActualCard = New cardValue With {.cardSuit = SuitValue, .cardName = CardValue}
End Sub

OOP concept: is it possible to update the class of an instantiated object?

I am trying to write a simple program that should allow a user to save and display sets of heterogeneous, but somehow related data. For clarity sake, I will use a representative example of vehicles. The program flow is like this:
The program creates a Garage object, which is basically a class that can contain a list of vehicles objects
Then the users creates Vehicles objects, these Vehicles each have a property, lets say License Plate Nr. Once created, the Vehicle object get added to a list within the Garage object
--Later on--, the user can specify that a given Vehicle object is in fact a Car object or a Truck object (thus giving access to some specific attributes such as Number of seats for the Car, or Cargo weight for the truck)
At first sight, this might look like an OOP textbook question involving a base class and inheritance, but the problem is more subtle because at the object creation time (and until the user decides to give more info), the computer doesn't know the exact Vehicle type.
Hence my question: how would you proceed to implement this program flow? Is OOP the way to go?
Just to give an initial answer, here is what I've came up until now. There is only one Vehicle class and the various properties/values are handled by the main program (not the class) through a dictionary. However, I'm pretty sure that there must be a more elegant solution (I'm developing using VB.net):
Public Class Garage
Public GarageAdress As String
Private _ListGarageVehicles As New List(Of Vehicles)
Public Sub AddVehicle(Vehicle As Vehicles)
_ListGarageVehicles.Add(Vehicle)
End Sub
End Class
Public Class Vehicles
Public LicensePlateNumber As String
Public Enum VehicleTypes
Generic = 0
Car = 1
Truck = 2
End Enum
Public VehicleType As VehicleTypes
Public DictVehicleProperties As New Dictionary(Of String, String)
End Class
NOTE that in the example above the public/private modifiers do not necessarily reflect the original code
Let's first distinguish between the set of answers which one can ask about an object in the garage (its attributes) from the set of answers to those questions( its state).
If you are simply looking at a scenario where the set of answers changes, then a simple State Pattern applies. The attributes remain constant, and state changes. All object instantiations remain of a single type with constant attributes.
if you are looking at the more complicated situation where the available attributes for an object in the garage changes, one uses the Decorator pattern. However, I don't think this quite fits your scenario either. The Decorator pattern is for scenarios where there is a tractable number of attributes, but the number of possible combinations is potentially exponential because there is no restriction of which go with which.
The scenario that I think best handles your situation is that the object is actually undefined until identified, with only a Proxy (represented by the vehicle key) created initially, Once the object is completely identified, which it seems occurs all at once, it's full object is instantiated.
It is possible that you might want a Decorator sitting on top of the Proxy, but that might not be necessary either.
--Later on--, the user can specify that a given Vehicle object is in fact a Car object or a Truck object
You are dangerously close to asking for unrestricted downcasting as a feature. This is just not possible in managed code, the CLR provides hard guarantees that illegal downcasts are never possible. It raises the InvalidCastException when you try anyway.
Somewhat more concrete, if the original object was created as a Vehicle then there is no way that you can ever interpret or access that object as though it is a Truck. A Truck has, say, a Cargo property that Vehicle doesn't have. In fact, Vehicle doesn't even have the storage for Cargo. Re-interpreting a Vehicle as a Truck will give it a complete garbage value for Cargo. And much worse, writing the Cargo property will corrupt memory.
Unrestricted downcasting is possible in some languages, like C and C++. Particularly in C it is almost inevitable, void* is the "object class" of C. But these languages are also pretty famous for writing code that crashes at runtime. An illegal downcast is an excellent and common way to induce such a crash. The heap corruption this causes is extremely difficult to diagnose, the crash doesn't happen until much later, far removed from where the original damage was done.
You use the standard Factory pattern to create instances of a specific class that have a desired set of properties. Upcasting to the base class is always valid. Such a factory will return a reference of type Vehicle for example, even though it created a Truck object. Downcasting it later to a Truck will be valid.
Object Oriented Programming works best when you try to model realistic objects, rather than 'magical' objects that do things that don't make sense.
In the real world you can't have a car that is a vague blob, but suddenly becomes a Pickup truck. Thus it makes little sense to model your system this way, and you will run into various problems that cause you to go back to the "magic" again and again.
One can think of the compiler and the runtime environment as a sort of "pocket universe" and one can think of certain rules enforced by the compiler as "Laws of physics" that apply in that universe. In some cases you bend these laws, given certain compensations, but in general you shouldn't try to do this as it can cause huge rifts in the space-time continuum (ie, you can corrupt the internal state of the program).
Instead, I would model it this way. You can have a list of "License Plate" objects, and when you want to "create" a Pickup Truck, you use a Factory class, passing in the License Plate object and it will create a Pickup Truck that uses that license object.
Remember, that objects often contain other objects. A license plate is an object in and of itself, so why not treat it as such? Since you appear to have no real tie between the ambiguous "vehicle" and the license plate, this makes more sense.
My understanding is : You are trying to achieve in VB.net something you can actually dynamically do in JavaScript and its constructors...
I don't know if you can dynamically create Methods, Functions, Events or Properties in VB.net like :
Public Module SampleMembers
Public _PaxNum As Integer = 0
Public _CargoAmount As Integer = 0
Public Function GetPassengerNumbers() As Integer
Return _PaxNum
End Function
Public Function GetCargoAmount() As Integer
Return _CargoAmount
End Function
End Module
And then, declare in your application a basic object like :
Dim MyVehicle As Object
Later on, during runtime, dynamically add members to your vehicle object like :
Public Sub ConvertBaseVehicleToCar(ByRef CurrentVehicle As Object)
' ...
Object.AddMember(SampleMember._PaxNum, CurrentVehicle)
Object.AddMember(SampleMember.GetPassengerNumber(), CurrentVehicle)
' Where Object would have a magical Constructor Modyfier...
' That would be GREAT... of course
End Sub
But you can't do that in VB.net if I'm not mistaken
If it was just about datas...
I would use :
Public Class Vehicle
Private _PropertiesList As New SortedList(Of String, String)
Public Function AddProperty(ByVal PropertyName As String, ByVal PropertyValue As String) As Boolean
If _PropertiesList.ContainsKey(PropertyName) Then
_PropertiesList.Item(PropertyName) = PropertyValue
Return False ' Property replaced !
Else
_PropertiesList.Add(PropertyName, PropertyValue)
Return Property ' New Property added !
End If
End Function
Public Function RemoveProperty(ByVal PropertyName) As Boolean
If _PropertiesList.ContainsKey(PropertyName) Then
_PropertiesList.Remove(PropertyName)
Return True ' Property actually removed !
Else
Return False ' No property with that name !
End If
End Function
Public Function GetPropertiesList() As List(Of String)
Dim NewList As New List(Of String)
Dim CurrentProperty As String
For Each CurrentProperty In _PropertiesList.Keys
NewList.Add(CurrentProperty)
Next
Return NewList
End Function
Public Function GetProperty(ByVal PropertyName As String) As String
If _PropertiesList.ContainsKey(PropertyName) Then
Return _PropertiesList.Item(PropertyName)
Else
Return ""
' Or whatever explicit code of your choice
' like Return "N/A" or Return "#"
End If
End Function
' I would replace this latest function by
Public Property Item(ByVal PropertyName As String) As String
' ...
End Property
' ...
' And the Constructor
Public Sub New(ByVal VehicleType As String)
InitializeType(VehicleType)
End Sub
' With its default Properties like :
Private Sub InitializeType(ByVal ProposedType As String)
ProposedType = ProposedType.Trim().ToUpper()
Select Case ProposedType
Case "CAR":
Item("Type") = "CAR"
Case "TRUCK":
Item("Type") = "TRUCK"
Case "MINIVAN":
Item("Type") = "MINIVAN"
End Select
End Sub
' And add a FINAL ReadOnly Property
Public ReadOnly Property VehicleType() As String
Get
Return Item("Type")
End Get
End Property
End Class
Now, MyVehicle could be anything, a car, a truck, a plane, even PlanetEarth...
Still, I CAN'T mask or add methods, functions, properties upon runtime. My properties are all of type "String"
MyCar.Item("NumberOfWheels") = "6"
' ^^ I'll have to cast this to Integer before using it...
MessageBox.Show(SumOfWheels(MyListOfVehicles).ToString())
' Where :
Public Function SumOfWheels(ByVal ListOfVehicles As List(Of Vehicles)) As Integer
Dim CurrentVehicle As Vehicle
Dim CurrentWheels As Integer
Dim TotalWheels As Integer = 0
For Each CurrentVehicle In ListOfVehicles
If Integer.TryParse(CurrentVehicle.Item("NumberOfWheels"), CurrentWheels)
TotalWheels = TotalWheels + CurrentWheels
End If
Next
Return TotalWheels
End Function
However, I could add a sort of virtual type modyfier : The initial ReadOnly Property VehicleType()
' ...
Public Property VehicleType() As String
' The Getter is the same, but the setter is a litte bit different :
Set(ByVal NewType As String)
InitializeType(NewType) ' Simple ? No ! I'll have to edit the Method...
End Set
End Property
Private Sub InitializeType(ByVal ProposedType As String)
ProposedType = ProposedType.Trim().ToUpper()
Select Case ProposedType
Case "CAR":
Item("Type") = "CAR"
RemoveProperty("CargoHold")
Item("Drivers") = "1"
Case "TRUCK":
Item("Type") = "TRUCK"
RemoveProperty("PaxSeats") ' Well, you actually can have one.. or two..
Item("Drivers") = "1"
Case "MINIVAN":
Item("Type") = "MINIVAN"
Item("Drivers") = "1"
Case "MOTORBIKE":
Item("Type") = "MOTORBIKE"
RemoveProperty("CargoHold")
Item("Drivers") = "1"
Item("PaxSeats") = "1"
Item("NumberOfWheels") = "2"
Case "JETLINER":
Item("Type") = "JETLINER"
Item("Drivers") = "2"
Case "VINTAGEJETLINER":
Item("Type") = "VINTAGEJETLINER"
Item("Drivers") = "3"
End Select
End Sub
' ...
Anyway, I'll have to write codes for specific routines using several vehicles in my Garage. This would be members in my Garage Class. Each time I want to do specific things for a given set of vehicles, I'll had to check what type of vehicle it is and select the correct path of code to run.........
That would become very tricky if you want to have a bunch of sub models of vehicles...
' VEHICLE>MINIVAN
' VEHICLE>MINIVAN>CITROEN
' VEHICLE>MINIVAN>CITROEN>3CV
' VEHICLE>MINIVAN>CITROEN>3CV>BASIC
' VEHICLE>MINIVAN>CITROEN>3CV>COLLECTOR
' VEHICLE>MINIVAN>CITROEN>3CV>DEADHULK
But at least, you can have an usefull Function that retrives ALL vehicles with a specific property in your Garage :
Public Function GetVehicleUsingProperty(ByVal PropertyName As String, ByVal PropertyValue As String) As List(Of Vehicle)
' And a better one :
Public Function GetVehicleUsingProperty(ByVal PropertiesParam As SortedList(Of String, String)) As List(Of Vehicle)
' ... :P
Just the way I see things. Hope someone else could give a better way to implement all of this ?
I would be inclined to have a "Vehicle" base class that can be created normally (not abstract) with the basic properties that you do know. Including the VehicleType you defined set to "Generic" by default.
Create each specific type for each sub type. Defining the correct properties in a ridgid format to enforce good code.
In the base type create a function to Clone Vehicle Properties to a passed in Object. Eg.
Public sub CloneTo(byval OtherVehicle as Vehicle)
When a "Generic" vehicle needs to be more specific create the new child type, pass it to the routine to clone the existing information, and replace the old type with the new one in the Garage Collection.
You would need to evaluate the Child Type for each item in the garage collection to determine the available extended properties, but I think a good solid full tree list of children can minimize this work if all the correct levels are in place (the lower levels would be most commonly accessed and if any properties that can be are placed always at the highest level in the tree) Eg. Vehicle - Car - Sedan. PassengerCapacity for exampel is really a property of a Vehicle.

why are overloaded private shared function not accessible when called from shared function in same class

Came across something I found interesting and would love an explanation.
Edit
This question is not meant to be answered with what should be done to fix it. I know the fixes. I want an explanation of why the compiler does what it does. Ex. Are the private functions not considered given this scenario?
Problem
I have a class that has a public shared(static) function called WhatIs. WhatIs takes a parameter that has a collection of objects. the code iterates over this collection and calls a WhatIs function that has a parameter matching type of what the object is.
When executed, an InvalidCastException exception is thrown because the execution is trying to call the WhatIs function that started this, not the one for the type provided.
That's weird, but what made it odd to me was when you change the private shared functions to public shared then it works fine.
Even odder, when you explicit cast the object then it works even if the function is private.
What?! someone please explain
Code
the guts:
Public Class House
Public Property Furniture As ICollection(Of Object)
Public Sub New()
Furniture = New List(Of Object)
End Sub
End Class
Public Class Chair
Public Property IsComfortable As Boolean
End Class
Public Class Table
Public Seats As Integer
End Class
Public Class HouseExaminer
Public Shared Function WhatIs(thing As House) As String
Dim isA As String = "a house that contains "
For Each item In thing.Furniture
isA &= WhatIs(item)
Next
Return isA
End Function
Private Shared Function WhatIs(thing As Chair) As String
Return "a " & If(thing.IsComfortable, "comfortable", "uncomfortable") & " chair "
End Function
Private Shared Function WhatIs(thing As Table) As String
Return "a table that seats " & thing.Seats & " iguanas"
End Function
End Class
to test
Imports System.Text
Imports Microsoft.VisualStudio.TestTools.UnitTesting
Imports stuff
<TestClass()>
Public Class HouseExaminerTests
<TestMethod()>
Public Sub TestWhatIs()
Dim given As New House()
Dim expected As String
Dim actual As String
given.Furniture.Add(New Chair() With {.IsComfortable = True})
given.Furniture.Add(New Table() With {.Seats = 4})
expected = "a house that contains a comfortable chair a table that seats 4 iguanas"
actual = HouseExaminer.WhatIs(given)
Assert.Equals(expected, actual)
End Sub
End Class
result
debug the test and you get this:
InvalidCastException
Method invocation failed because 'Public Shared Function WhatIs(thing As stuff.House) As String' cannot be called with these arguments:
Argument matching parameter 'thing' cannot convert from 'Chair' to 'House'.
These changes make it work but why?!
make em public
change the private shared functions in HouseExaminer to public, rerun test. spoiler, it works
explicitly cast the objects
change them back to private then replace
isA &= WhatIs(item)
with
If TypeOf item Is Chair Then isA &= WhatIs(CType(item, Chair))
If TypeOf item Is Table Then isA &= WhatIs(CType(item, Table))
rerun test, and what do u know, it works
Firstly, it looks like you have implicit conversions turned on. That is the start of the issue. Secondly, you define Furniture as a List(of Object). Your first call to WhatIs is succeeding. The compiler is making a best guess as to which overload to use when passing what it sees as simply Object as it iterates through thing.Furniture, and it determines the public static version of the WhatIs method to be the most appropriate. It then attempts to implicitly convert Object to House, and inevitably fails.
Why does casting work? Because it takes the guess work out of determining which overload to use.
Moral of the story is: Don't make the compiler guess. Implicit conversion can lead to tricky bugs.
Edit: Why doesn't the compiler see the other overloaded functions?
The compiler has to determine the correct overload to use at compile time. It does not wait until runtime to determine which overload to use, and therefore doesn't have the luxury of inspecting the type of the object to determine the most appropriate overload.
Since the compiler only knows that furniture is a List(Of Object), technically (with implicit conversion turned on) all three of the overloads are deemed "appropriate," but the compiler must choose one. It ranks the possible overload candidates, and chooses the public version ahead of the private ones.
Use always
Option Strict On
You cannot make it more flexible by adding Methods equal in name, just with different parametertypes.
Update
Private Function ShowMe(data As Integer) As String
Return data.ToString
End Function
Private Function ShowMe(data As String) As String
Return data
End Function
Private Function ShowMe(data As Double) As String
Return data.ToString
End Function
Dim bla As New List(Of Object)
if you then call
bla.Add(12)
bla.Add("hi")
bla.Add(1.2)
Dim text As String
text = ShowMe(bla(0))
text = ShowMe(bla(1))
text = ShowMe(bla(2))
then the compiler will always complain that the correct method does not exist, because the correct method is not selected by checking the type, instead it is selected by the definition, for which type the container is defined for.
Private Function ShowMe(data As Object) As String
Return data.ToString
End Function
this would be called for all integer, doubles and strings. If it is not available, then some methods are used that can do some kind of automatic conversion. Thats why you can put an integer in a float, or put a number in a string.
One way would be to check for its type and do an explizit type conversion
For Each ele As Object In bla
If TypeOf ele Is Integer Then
text = ShowMe(CInt(ele))
ElseIf TypeOf ele Is Double Then
text = ShowMe(CDbl(ele))
Else
text = ShowMe(CStr(ele))
End If
Next
But this is still not so clean. If you want to access properties that all objects should support, then put them in a container and define the type as something that assures that those properties exist.