I've inherited some code and it is making me cringe when I look at it. Is there more elegant way to write the following?
Dim myItem As DTO.MyBaseClass = Nothing
Dim myType As String = GetTypeString()
Select Case myType
Case Is = "Case1"
myItem = Bus.BusManager(Of DTO.MyClass1).Read()
Case Is = "Case2"
myItem = Bus.BusManager(Of DTO.MyClass2).Read()
'... etc etc for 30 lines
Is there a way to make a map from the string to the class type and then just have a line like so? Or something similar?
myItem = Bus.BusManager(Of MappingDealy(myType)).Read()
Since BusManager is a Generic, the type you pass into Of <type> must be specified at compile time. It's not like a traditional parameter that you can change at runtime.
It's unclear from the code you listed what BusManager actually does. If all it's doing is creating an instance of the Generic type, then maybe the person who created it doesn't really understand generics. Do you have the ability to rework how BusManager works, or are you limited to using it as is?
As #jmoreno mentioned, you can use reflection to create an instance of a type from a string containting the name of the type. Here's how that would work:
Imports System.Reflection
Imports System.IO
Public Class ObjectFactory
Private Shared Function CreateObjectFromAssembly(ByVal assembly As Assembly, ByVal typeName As String) As Object
' resolve the type
Dim targetType As Type = assembly.GetType(typeName)
If targetType Is Nothing Then
Throw New ArgumentException("Can't load type " + typeName)
End If
' get the default constructor and instantiate
Dim types(-1) As Type
Dim info As ConstructorInfo = targetType.GetConstructor(types)
Dim targetObject As Object = info.Invoke(Nothing)
If targetObject Is Nothing Then
Throw New ArgumentException("Can't instantiate type " + typeName)
End If
Return targetObject
End Function
Public Shared Function CreateObject(ByVal typeName As String) As Object
Return CreateObjectFromAssembly(Assembly.GetExecutingAssembly, typeName)
End Function
Public Shared Function CreateObject(ByVal typeName As String, ByVal assemblyFileName As String) As Object
Dim assemblyFileInfo = New FileInfo(assemblyFileName)
If assemblyFileInfo.Exists Then
Return CreateObjectFromAssembly(Reflection.Assembly.LoadFrom(assemblyFileName), typeName)
Else
Throw New ArgumentException(assemblyFileName + " cannot be found.")
End If
End Function
End Class
In a production app, I'd probably set the return type for all of these methods to my base class or interface. Just make sure you pass in the full typeName including the Namespace.
With that factory class in place, then the elegant version of your code would look something like this:
Dim myItem as DTO.MyBaseClass = ObjectFactory.CreateObject("DTO." & GetTypeString())
First of all, never use Case Is = and never initialize to Nothing. So a quick one would be:
Dim myItem As DTO.MyBaseClass
Select Case GetTypeString()
Case "Case1"
myItem = Bus.BusManager(Of DTO.MyClass1).Read()
' etc etc
But since you're using templating, there's really no way to map it unless you want to use reflection, which is horribly inefficient at the expense of slightly cleaner and shorter code. You could also add an Imports DTO to save on 124 characters, and also Bus to save on another 120 characters.
Without seeing more of the code I would recommend use an Enumeration on my Case Statement to prevent minor bugs from appearing.
You could then either use a Factory Method to process the data based on the Enumeration.
Related
I've heard advice to change from User Defined Type (UDT) to a regular Class in order to overcome the limitations of UDT, such as not being able to use For Each with a UDT.
I've also heard advice to change from a regular Class to UDT to overcome the Class limitation where you can't pass things BYREF, like...
'Function:
Public Function RemoveArticle (ByRef strMovieTitle As String)
'Expected input is like "Terminator, The"
strMovieTitle = Left(... 'removes the article.
End Function
That works fine for this call:
Dim strMovieTitle As String
strMovieTitle = "Terminator, The"
RemoveArticle strMovieTitle
But not this call:
Dim objMovie As MovieClass
objMovie.strMovieTitle = "Terminator, The"
objMovie.strMovieGenre = "Sci-Fi"
InvertArticle objMovie.strMovieTitle
Even though MovieClass defines
strMovieTitle As String
I can't go changing RemoveArticle (and every simple little function like it) to take a MovieClass parameter instead of a String parameter because there are other UDTs or Classes and String Variables that also need to use RemoveArticle.
What do I do if I need to use For Each and I also need to pass ByRef?
Is there a way a Class can work around the parameter problem?
(Using Excel 2010.)
Now I have understood your concern.
You simply can't take that approach to meet your goal. As Tim Williams has commented in your question, your best bet would be something like this:
Dim objMovie As MovieClass
Dim strMovieTitle As String
strMovieTitle = "Terminator, The"
objMovie.strMovieTitle = InvertArticle(strMovieTitle)
However, I see that this still does not satisfy your need.
My suggestion is as follows:
make your object internal, target properties Private and expose them with Property Let and Property Get. This way you can do the modifications you want to the properties either on set or on get (from within the class... rather than fixing things from outside the class).
Aside note, in regards to create a helper class (as someone has recommended to you): you could join into one class all those functions you use widely, such as RemoveArticle or InvertArticle. However, it requires to create an instance object every time you want to use them and, therefore, does not combine well with the recommendation I am giving to you (if you want just to simplify code). So having them in a Module as you do now is fine. Just to clarify: those recommendations they gave to you are unrelated to your question here.
Example A: on set
In you class MovieClass, rename first all the instances of strMovieTitle to pStrMovieTitle and add this to your code:
Private pStrMovieTitle As String
Public Property Let strMovieTitle (strIn As String)
pStrMovieTitle = InvertArticle(strIn)
End Property
Public Property Get strMovieTitle As String
strMovieTitle = pStrMovieTitle
End Property
The usage would be something like this:
Dim objMovie As MovieClass
objMovie.strMovieTitle = "Terminator, The" ' the article position gets rectified on assignation
objMovie.strMovieGenre = "Sci-Fi"
'InvertArticle objMovie.strMovieTitle ' => you don't need to do this call
Example B: on get
To keep your original string as it comes, and do apply your helpers when you get the property value. That way you always preserve the original string. However, this approach will need more rework and it's only worthy in cases where you have lots of ways to use that String in different parts of your code.
Private pStrMovieTitleSource As String
Public Property Let strMovieTitle (strIn As String)
pStrMovieTitleSource = Trim(strIn)
End Property
Public Property Get strMovieTitleSource () As String
strMovieTitleSource = pStrMovieTitleSource
End Property
Public Property Get strMovieTitleRoot () As String
strMovieTitleRoot = RemoveArticle(pStrMovieTitleSource)
End Property
Public Property Get strMovieTitle () As String
strMovieTitle = InvertArticle(pStrMovieTitleSource)
End Property
Hope it helps
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.
I have a requirement where I need to have a "type safe" way of accessing property names, without actually instantiating an object to get to the property. To give an example, consider a method that takes as arguments a list of IMyObject and a string that represents a property name (a property that exists in IMyObject).
The methods implementation will take the list and access all the objects in the list using the property name passed... for some reason or another, we won't dwell on that!!
Now, I know that you can do this using an instantiated object, something like ...
Dim x as MyObject = nothing
Dim prop As PropertyInfo = PropHelper.GetProperty(Of MyObject)(Function() x.MyProperty)
Where my helper method uses reflection to get the name of the property as a string - there are numerous examples of this flying around on the web!
But I don't want to have to create this pointless object, I just want to do something like MyObject.MyProperty! Reflection allows you to iterate through a types properties and methods without declaring an object of that type... but I want to access a specific property and retrieve the string version of its name without iteration and without declaring an object of that type!
The main point here is that although I am trying to get the property name as a string... this is done at run time... at compile time, I want this to be type safe so if someone changes the property name, the compilation will break.
Can anyone help in this quest!?!
So here is a quick code-listing to demonstrate the answer that I was looking for:
Imports System.Linq.Expressions
Public Class A
Public Prop1 As String
Public Prop2 As Integer
End Class
Public Class Form1
Public Function GetPropertyNameB(Of TModel, TProperty)(ByVal [property] As Expression(Of Func(Of TModel, TProperty))) As String
Dim memberExpression As MemberExpression = DirectCast([property].Body, MemberExpression)
Return memberExpression.Member.Name
End Function
Public Sub New()
InitializeComponent()
Dim propertyName As String = GetPropertyNameB(Function(myObj As A) myObj.Prop1)
Dim propertyName2 As String = GetPropertyNameB(Function(myObj As A) myObj.Prop2)
MsgBox(propertyName & " | " & propertyName2)
End
End Sub
End Class
You may be able to pass the property in as a simple lamdba expression, and take it in the method as an expression tree. You should be able to analyze the expression tree to get the string name of the property, but it the lambda expression will fail to compile if the property name changes. Check out this page for more details:
http://msdn.microsoft.com/en-us/library/bb397951.aspx
You can make use of the NameOf function:
Dim fieldName = nameOf(MyClass.MyField)
Is it possible using Reflection or some other method to obtain a reference to a specific class instance from the name of that class instance?
For example the framework for the applications i develop heavily uses public class instances such as:
Public bMyreference as MyReference = new MyReference
Then throughout the application bMyReference is used by custom controls and code.
One of the properties of the custom controls is the "FieldName" which references a Property in these class instances (bMyReference.MyField) as a string.
What i would like to be able to do is analyze this string "bMyReference.MyField" and then refer back to the actual Instance/Property.
In VB6 I would use an EVAL or something simular to convert the string to an actual object but this obviously doesn't work in VB.net
What I'm picturing is something like this
Dim FieldName as String = MyControl.FieldName ' sets FielName to bMyReference.MyField
Dim FieldObject() as String = FieldName.Split(".") ' Split into the Object / Property
Dim myInstance as Object = ......... ' Obtain a reference to the Instance and set as myInstance
Dim myProperty = myInstance.GetType().GetProperty(FieldObject(1))
I don´t know if I´ve understood you well, but my answer is yes, you can do it by reflection. You´ll need to import System.Reflection namespace.
Here is an example:
' Note that I´m in namespace ConsoleApplication1
Dim NameOfMyClass As String = "ConsoleApplication1.MyClassA"
Dim NameOfMyPropertyInMyClass As String = "MyFieldInClassA"
' Note that you are getting a NEW instance of MyClassA
Dim MyInstance As Object = Activator.CreateInstance(Type.GetType(NameOfMyClass))
' A PropertyInfo object will give you access to the value of your desired field
Dim MyProperty As PropertyInfo = MyInstance.GetType().GetProperty(NameOfMyPropertyInMyClass)
Once you have MyProperty, uou can get the value of your property, just like this:
MyProperty.GetValue(MyInstance, Nothing)
Passing to the method the instace of what you want to know the value.
Tell me if this resolve your question, please :-)
EDIT
This would be ClassA.vb
Public Class MyClassA
Private _myFieldInClassA As String
Public Property MyFieldInClassA() As String
Get
Return _myFieldInClassA
End Get
Set(ByVal value As String)
_myFieldInClassA = value
End Set
End Property
End Class
I have a Function that returns a struct in VB.NET:
Public Shared Function StoreData(Byval abc as store) as pcStruct
Dim st as new pcStruct
For each pc as pent in abc.route
st.stra.Add("test")
next
st.message="Successfully uploaded"
Return st
End Function
Where as in my struct I have two variables:
stra is an Arraylist
message is a string
When I try to populate the structure as shown above, I get an a nullvalue exception.
What am I doing wrong?
The st.stra is not initialized
Try something like:
Dim arr as new ArrayList()
st.stra=arr
before you use st.stra.Add
Ngu's answer is most likely the problem.
Specifically, remember that Structures can have constructors. If you haven't written one for your pcStruct structure, you might want to consider it:
Public Structure pcStruct
Private thisMessage As String
Private thisList as ArrayList
Public Sub New()
thisList = New ArrayList()
End Sub
Public Property stra As ArrayList
Get
Return thisList
End Get
End Property
Public Property Message As String
Get
Return thisMessage
End Get
Set
thisMessage = Value
End Set
End Property
End Structure
Some argue that it's not necessary to do this in the constructor, but that you should simply do it where the member variable is declared. There's some evidence that this performs better, but I would argue that this will depend on how many of these objects you're declaring. If you're not declaring vast amounts of them, the performance benefit does not outweigh the clarity and extensibility benefit of placing your initialization code in one place. But, as must be pointed out, that is entirely my opinion and must be taken with both a grain of salt and an air freshener.