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.
Related
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)
Been working a lot with custom classes lately and I love the power you can have with them but I have come across something that I'm not able to solve and/or find anything helpful online.
I have a list of a class with properties I'm looking to only store information pulled from a database into.
Public Class CustomClass
Public _Values As String
Public _Variables As String
Public ReadOnly Property Values() As String
Get
Return _Values
End Get
End Property
Public ReadOnly Property Variables() As String
Get
Return _Variables
End Get
End Property
Sub New(ByVal values As String, ByVal variables As String)
_Values = values
_Variables = variables
End Sub
End Class
I will be iterating through some database entries, and I'm looking to store them into the appropriate property when I hit them (since I won't have them all available immediately, which is part of my problem). I want to just be able to add either the value or the variable at a time and not both of them, but since I have the sub procedure 'New' passing two arguments, it will always require passing them both. I've found the only way around this is by making them optional fields which I don't feel is the right way to solve this. Is what I'm looking to do possible with a class or would it be simpler by using a structure?
You can overload the constructor:
Friend Class Foo
' using auto-implement props:
Public Property Name As String ' creates a _Name backing field
Public Property Value as Integer
Public Sub New(newN as String, newV as Integer)
' access "hidden" backing fields if you want:
_Name = newN
_Value = newV
End Sub
Public Sub New() ' simple ctor
End Sub
Public Sub New(justName As String)
' via the prop
Name = justName
End Sub
End Class
You now have 3 ways to create the object: with full initialization, partial (name only) or as a blank object. You will often need a "simple constructor" - one with no params - for other purposes: serializers, Collection editors and the like will have no idea how to use the parameterized constructors and will require a simple one.
If rules in the App were that there was no reason for a MyFoo to ever exist unless both Name and Value being defined, implementing only the New(String, Integer) ctor enforces that rule. That is, it is first about the app rules, then about coding convenience.
Dim myFoo As New Foo ' empty one
myFoo.Name = "ziggy" ' we only know part of it
Since the default of string is nothing, you could pass nothing for the value you don't have. IE
Collection.Add(New CustomClass("My Value",Nothing))
Every type has a default, so this works with more than just strings.
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.
OK, that question sounds maybe a little confusing so I'll try to explain it with an example.
Pretend you have an object like this:
Class Something
Private varX As New Integer
Private varY As New String
'[..with the associated property definitions..]
Public Sub New()
End Sub
End Class
And another with:
Class JsonObject
Inherits Dictionary(Of String, String)
Public Function MakeObject() As Object 'or maybe even somethingObject
Dim somethingObject As New Something()
For Each kvp As KeyValuePair(Of String, String) In Me
'Here should happen something to use the Key as varX or varY and the Value as value for the varX or varY
somethingObject.CallByName(Me, kvp.Key, vbGet) = kpv.Value
Next
return somethingObject
End Function
End Class
I've got the 'CallByMe()' function from a previous question of myself
CallByName works different from the way you are trying to use it. Look at the documentation, it will tell you that in this particular case the correct usage would be
CallByName(Me, kvp.Key, vbSet, kpv.Value)
However, the function CallByName is part of a VB library that isn’t supported on all devices (notably it isn’t included in the .NET Mobile framework) and consequently it’s better not to use it.
Using proper reflection is slightly more complicated but guaranteed to work on all platforms.
Dim t = GetType(Something)
Dim field = t.GetField(kvp.Key, BindingFlags.NonPublic Or BindingFlags.Instance)
field.SetValue(Me, kvp.Value)
Rather than giving the very specific case (which I did earlier), let me give a general example. Let's say that I have a function, called callingFunction. It has one parameter, called parameter. Parameter is of an unknown type. Let us then say that I wish to copy this parameter, and return it as a new object. For example, in pseudo code, something along the lines of...
Function callingFunction(ByVal parameter As Object) As Object
Dim newObj As New Object
'newObj has the same value as parameter, but is a distinctly different object
'with a different reference
newObj = parameter
return newObj
End Function
EDIT: Additional Information
The first time I posted this question, I received only one response - I felt that perhaps I made the question too specific. I guess I will explain more, perhaps that will help. I have an ASP page with 10 tables on it. I am trying, using the VB code behind, to come up with a single solution to add new rows to any table. When the user clicks a button, a generic "add row" function should be called.
The difficulty lies in the fact that I have no guarantee of the contents of any table. A new row will have the same contents as the row above it, but given that there are 10 tables, 1 row could contain any number of objects - text boxes, check boxes, etc. So I want to create a generic object, make it of the same type as the row above it, then add it to a new cell, then to a new row, then to the table.
I've tested it thoroughly, and the only part my code is failing on lies in this dynamic generation of an object type. Hence why I asked about copying objects. Neither of the solutions posted so far work correctly, by the way. Thank you for your help so far, perhaps this additional information will make it easier to provide advice?
You can't do this in general. And it won't be a good idea, for example, if parameter is of a type which implements the singleton pattern. If parameter is of a type which supports copying, it should implement the ICloneable interface. So, your function could look like this:
Function MyFunc(ByVal parameter As Object) As Object
Dim cloneableObject As ICloneable = TryCast(parameter, ICloneable)
If Not cloneableObject Is Nothing Then
Return cloneableObject.Clone()
Else
Return Nothing
End If
End Function
You could implement something like this:
Dim p1 As Person = New Person("Tim")
Dim p2 As Object = CloneObject(p1)
Dim sameRef As Boolean = p2 Is p1 'false'
Private Function CloneObject(ByVal o As Object) As Object
Dim retObject As Object
Try
Dim objType As Type = o.GetType
Dim properties() As Reflection.PropertyInfo = objType.GetProperties
retObject = objType.InvokeMember("", System.Reflection.BindingFlags.CreateInstance, Nothing, o, Nothing)
For Each propertyInfo As PropertyInfo In properties
If (propertyInfo.CanWrite) Then
propertyInfo.SetValue(retObject, propertyInfo.GetValue(o, Nothing), Nothing)
End If
Next
Catch ex As Exception
retObject = o
End Try
Return retObject
End Function
Class Person
Private _name As String
Public Property Name() As String
Get
Return _name
End Get
Set(ByVal value As String)
_name = value
End Set
End Property
Public Sub New()
End Sub
Public Sub New(ByVal name As String)
Me.Name = name
End Sub
End Class
Here's a simple class that will work for most objects (assumes at least .Net 2.0):
Public Class ObjectCloner
Public Shared Function Clone(Of T)(ByVal obj As T) As T
Using buffer As MemoryStream = New MemoryStream
Dim formatter As New BinaryFormatter
formatter.Serialize(buffer, obj)
buffer.Position = 0
Return DirectCast(formatter.Deserialize(buffer), T)
End Using
End Function
End Class