I am trying to automatically clone an object without having to instantiate a new one and manually copy every single variable.
I remember back in the day (when I did VB6 everyday) I came up with a method of cloning objects using the PropertyBag, which was pretty cool. But I've lost the code and don't remember how to do it anymore.
Does anyone remember or have another method?
Is this what you were looking for? Article is copied below for posterity.
Serialize Data Using a PropertyBag
You can serialize your data quickly by placing it into a PropertyBag object, then reading the PropertyBags Contents property. This property is really a Byte array that is a serial representation of the data in your PropertyBag object. You can use this byte array for many purposes, including an efficient means of data transmission over DCOM:
Private Function PackData() As String
Dim pbTemp As PropertyBag
'Create a new PropertyBag object
Set pbTemp = New PropertyBag
With pbTemp
'Add your data to the PB giving each item a
'unique string key
Call .WriteProperty("FirstName", "John")
Call .WriteProperty("MiddleInitial", "J")
Call .WriteProperty("LastName", "Doe")
'Place the serialized data into a string
'variable.
Let PackData = .Contents
End With
Set pbTemp = Nothing
End Function
To retrieve the serialized data, simply create a new PropertyBag object and set the serialized string to its Contents property. Convert the string into a byte array before assigning it to the Contents property:
Private Sub UnPackData(sData As String)
Dim pbTemp As PropertyBag
Dim arData() As Byte
'Convert the string representation of the data to
'a Byte array
Let arData() = sData
'Create a new PropertyBag object
Set pbTemp = New PropertyBag
With pbTemp
'Load the PropertyBag with data
Let .Contents = arData()
'Retrieve your data using the unique key
Let m_sFirstName = .ReadProperty("FirstName")
Let m_sMiddleInitial = _
.ReadProperty("MiddleInitial")
Let m_sLastName = .ReadProperty("LastName")
End With
Set pbTemp = Nothing
End Sub
Mike Kurtz, McKees Rocks, Pa.
Method I have used in the past is putting all instance variables on a UDT. As long as you keep the UDT up to date you can copy a class' data with a single method/statement.
Given a "Person" class here's a simple example:
Private Type tPerson
ID As Long
FirstName As String
LastName As String
End Type
Private m_Person As tPerson
Public Sub InitPerson(ID As Long, FirstName As String, LastName As String)
m_Person.ID = ID
m_Person.FirstName = FirstName
m_Person.LastName = LastName
End Sub
Friend Sub SetData(PersonData As tPerson)
m_Person = PersonData
End Sub
Public Function GetClone() As Person
Dim p As New Person
p.SetData m_Person
Set GetClone = p
End Function
Public Property Get FirstName() As String
FirstName = m_Person.FirstName
End Property
To try the code:
Dim p As New Person
p.InitPerson 1, "MyName", "MyLastName"
Dim p2 As Person
Set p2 = p.GetClone
MsgBox p2.FirstName
If you maintain all instance varianbles inside the UDT instead of declaring them seperately you can have simple Clone method that needs very little maintenance.
Another advantage is you can put a UDT to a file handle for quick serialization to disk.
Public Sub Save(filePathName As String)
Dim f As Integer
f = FreeFile()
Open filePathName For Binary Access Write Lock Read Write As #f
Put #f, , m_Person
Close #f
End Sub
A poor men's serialization solution really :-)
Also read: Persisting a Component's Data.
Every object in my application's framework has a Read and Store method. What I do is pass them a stream that writes to a bytearray and store the byte array. While this doesn't eliminate having to handle every property you only have to deal with this once for reading and once for writing.
An alternative is to use a property bag like Darrel Miller says but you still have to deal with each property separately. If you already have read and store then my suggestion should save some time.
Related
Imagine this call which uses three fluent API's to make a very simple and readable function (the IQueryable Where and OrderBy, the AutoMapper Project and To, and ToDataSourceResult from Kendo).
Return Json(DataSource.TemplateItems.Where(Function(x) x.TemplateID= id) _
.OrderBy(Function(x) x.SortOrder) _
.Project() _
.To(Of TemplateIdRowViewModel) _
.ToDataSourceResult(req))
For debugging, I sometimes want to examine the current value being passed around in between calls to the fluent API. For example, I might want to know what my IQueryable looks like after Project and To finish with it but before ToDataSourceResult gets it.
I'm using the VS 2010 debugger. Right now I find myself editing the call to include temporary variable for each step so I can examine them, like this:
Dim where = DataSource.TemplateItems.Where(Function(x) x.TemplateID = id)
Dim ordered = where.OrderBy(Function(x) x.SortOrder)
Dim projected = ordered.Project().To(Of TemplateIdRowViewModel)()
Dim result = projected.ToDataSourceResult(req)
Dim jsonified = Json(result)
Return jsonified
I have this Sub which does the calculation get the information from CSV file.
Private Sub getTotalAmt(intDuration As integer, strProgrameType As String)
I've another Sub:
Private Sub getMembershipDiscount(ByRef dtDOB As Date, dblTotalAmt as Double)
which calculates the age of the customer and based on their age, gives them discount. So to do this, I've got to get the total amount from the above Sub (getTotalAmt) to this Sub procedure.
My question is, how to receive total amount from that sub procedure?
by definition, subroutines don't have output.
Instead, you want to write a function. In your case, change the code you have to something like this:
private function getTotalAmt(intDuration As integer, strProgrameType As String) as double
'do the same maths you do in the sub, and store your answer in a variable called "whatever"
getTotalAmt = whatever
end function
then to use the function:
sub IUseFunctions()
dim myDiscount as double
myDiscount = getTOtalAtm(1,"s")
end sub
In this example case, I've decided for you that the discount value is stored in a double type number. You can change that to whatever you like, at the top line of the getTotalAmt function.
Please speicify return types and convert your Sub to a Function
In that way you can always return something from your Functions,
if you specify a return type to your getTotalAmt(int Duration As Integer, StrProgrameType As String) method , That would be look like this Function getTotalAmt(int Duration As Integer, StrProgrameType As String) As double
then from another method you can call it and use retuned value of it for further calculation or whatever.
i.e
Private Sub sub1()
//You use the Sub2's value in here like
msgbox(sub2)
End Sub
Private Function sub2() As String //You can specifiy any data type you are returning, I specified String just for demnostrate
//Set the value of Sub2 in here
sub2 = "Yourvalue";
End Function
Please have a look at the following code, which I have run in VB6 and .NET:
Private Sub Form_Load()
Dim TestArray3() As String
TestArray3 = TestArrayFunction
End Sub
Private Function TestArrayFunction() As String()
Dim TestArray1(0 To 1) As String
Dim TestArray2() As String
TestArray1(0) = "Monday"
TestArray1(1) = "Tuesday"
TestArray2 = TestArray1
TestArray1(0) = "Wednesday"
End Function
When the program gets to the end of TestArrayFunction in VB6, the value of TestArray2(0) is "Monday", however when run in .NET, it is "Wednesday". I understand in .NET that an Array is an object and has two references pointing to it in TestArrayFunction. Why is this not the case in VB6?
This is to add to Dabblernl's response.
Long story made short, VB6 never really had a general reference type. Being built on COM, the only reference type it has (Dim ... As Object) is one for COM-based (or COM styled) classes. The Variant type was only good at boxing other types.
As to why ByRef works with arrays ...
The evolution of many dialects of BASIC, including VB6, adopted/adapted the paradigm of FORTRAN when it came to subroutines and functions; namely, parameters are passed by address (hence, ByRef is the default), and functions return their results by value. Add to this that BASIC never really had the concept of an address or a reference: VB6 (and earlier versions) would simulate addresses with 32-bit (signed) integers and otherwise would cope (in strange ways) via peculiar rules of the DECLARE SUB/FUNCTION statement and the (shoe-horned) AddressOf operator and VarPtr/StrPtr functions.
Final note: Since VB6 classes are COM-style, VB6 has the SET statement. The reason for this is that without the SET, an l-value = r-value situation is an implied LET statement. COM supports the notion of a default property (with and without parameters); LET objA = objB is interpreted as LET objA.DefaultPropOfA = objB.DefaultPropOfB. SET, on the other hand, makes objA take on the reference to the same object that objB references.
The Visual Basic of .NET is a nice and powerful language, with far fewer shortcomings and warts than VB6. However, it does have big differences from its legacy cousin. These differences have made long time VB6 users grouchy, and I suspect it has made many VB.NET users confused when they're asked to deal with VB6 code.
BTW, a VB6 coder would deal with the array within a class issue in this way:
Public Property Get DaysOfWeek(ByVal index As Integer) As String
DaysOfWeek = m_strDaysOfWeek(index)
End Property
Public Property Let DaysOfWeek(ByVal index As Integer, ByRef value As String)
m_strDaysOfWeek(index) = value
End Property
That would allow syntax of strDay = clsDateTime.DaysOfWeek(1) and clsDateTime.DaysOfWeek(2)="Tuesday" to work as desired.
I am struggling with this nearly daily. While it is perfectly possible to pass an array ByRef to a Function call, the '=' sign will make a shallow copy.
But there is more strange behaviour of arrays in VB6, Suppose you have the following DateTimeClass classmodule in VB6:
Option Explicit
Private m_strDaysOfWeek() As String
Public Property Get DaysOfWeek() As String()
DaysOfWeek = m_strDaysOfWeek()
End Property
Public Property Let DaysOfWeek(strDaysOfWeek() As String)
m_strDaysOfWeek() = strDaysOfWeek
End Property
Private Sub Class_Initialize()
ReDim m_strDaysOfWeek(7)
m_strDaysOfWeek(1) = "Monday"
End Sub
You would expect to be able to write code like:
Dim clsDateTime As New DateTimeClass
Dim strDay As String
strDay = clsDateTime.DaysOfWeek(1)
Or:
clsDateTime.DaysOfWeek(2)="Tuesday"
But you can't. You have to do it like this:
Dim clsDateTime As New DateTimeClass
Dim strDay As String
Dim strDays() As String
strDays = clsDateTime.DaysOfWeek
strDay = strDays(1)
strDays(2) = "Tuesday"
clsDateTime.DaysOfWeek = strDays
What the reasons of the VB6 team were at the time to implement it thus? I don't know...
I have abandoned the VB6 arrays and use Collections and Dictionaries nearly exclusively.
VB6 copies the array, and the last statement of your Function only changes the original copy.
Since you know .Net broke compatibility with VB I'm not sure where the confusion comes from.
I've got a loop, which is reading in a stack of XML files, for each one, it validates the data that was in the XML and loads it into some UDTs and then does some work on the data.
Then it gets back to the beginning of the loop and the UDTs still have data in from the previous XML. If that tag is defined in the new one, it overwrites, but if that tag isn't defined, then that element in the UDT is left alone.
But I can't reset the UDT by the technique I'd use for a variable (Let X = 0) unless I go through every single element of the UDT and reset the value. And doing it object-style (Set X as New UDT) doesn't work.
How do I do it?
Dim a new variable as the UDT and set the old one equal to the new variable.
For instance:
Dim XEmpty as UDT
X = XEmpty
Will reinitialise a variable X that is a UDT of type UDT.
You can use an empty utility function that simply returns the UDT
public function newTFoo() as TFoo
'//
end function
dim t as TFoo
t.x = 1234 ...
t = newTFoo()
'// t is reset
I'm trying to take the final solution from Phil Haack here and sort using his killer LINQ query but instead of using data context like he is, I want to query against IEnumerable(Of T) ... not having any luck.
Public Function DynamicGridData(ByVal sidx As String, ByVal sord As String, ByVal page As Integer, ByVal rows As Integer) As ActionResult
Dim list As List(Of User) = UserService.GetUserCollection()
Dim FilteredAndSortedList = list.OrderBy(sidx + " " + sord).Skip(pageIndex * pageSize).Take(pageSize)
Return JSON(jsonData)
End Function
Currently I get the error below my .OrderBy(sidx + " " + sord) line
"Data type(s) of teh type parameter(s) in extension method 'Public Function OrderBy(Of TKey)(keySelector As System.Func(Of User, TKey)) As System.Linq.IOrderedEnumerable(Of User)' defined in 'System.Linq.Enumerable' cannot be inferred from these arguments. Specifying the data type(s) explicitly might correct this error."
EDIT:
I found the issue to be that my "list" in the example above was not of type IQueryable. Simply adding .AsQueryable() did the trick!
Public Function DynamicGridData(ByVal sidx As String, ByVal sord As String, ByVal page As Integer, ByVal rows As Integer) As ActionResult
Dim list As List(Of User) = UserService.GetUserCollection()
Dim FilteredAndSortedList = list.AsQueryable().OrderBy(sidx + " " + sord).Skip(pageIndex * pageSize).Take(pageSize)
Return JSON(jsonData)
End Function
I'm not positive but I think it has to do with the fact that your passing into OrderBy a string as opposed to an Expression(TDelegate) which is what it expects.
Take a look at this article and see if it helps.
http://msdn.microsoft.com/en-us/library/bb549264.aspx
Edit:
I've taken a look at Phil's Code and at some further documentation provided by Scott Gu. You should be able to pass in a string in which to order by. That being the case my next question would be if you take out the order by does your app work? The reason for this is I'm wondering if we have another underlying error that we are missing. Like for instance you are returning JSON(jsondata) but I dont see where you are initializing jsondata. Now I dont think that is your error cause you state you are getting it on the orderby statement. However if you are actually getting the error because the object you are trying to order is empty then you have another underlying issue.
this worked for me
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx