Can I please have some help to perform a deep copy of an object.
Here is my code:
Option Explicit On
Option Strict On
<Serializable> Public Class [Class]
Private _Name As String
Private _ListOfFields As New List(Of Field)
Public Property Name As String
Get
Return _Name
End Get
Set(value As String)
_Name = value
End Set
End Property
Public Property ListOfFields As List(Of Field)
Get
Return _ListOfFields
End Get
Set(value As List(Of Field))
_ListOfFields = value
End Set
End Property
Public Function Clone() As [Class]
Return DirectCast(Me.MemberwiseClone, [Class])
End Function
End Class
Field is a Class that I have written myself as well.
What do I need to modify for the Clone() Function to return a deep copy?
You can create a clone of any class by calling this helper function:
Function DeepClone(Of T)(ByRef orig As T) As T
' Don't serialize a null object, simply return the default for that object
If (Object.ReferenceEquals(orig, Nothing)) Then Return Nothing
Dim formatter As New BinaryFormatter()
Dim stream As New MemoryStream()
formatter.Serialize(stream, orig)
stream.Seek(0, SeekOrigin.Begin)
Return CType(formatter.Deserialize(stream), T)
End Function
This works by serializing all the information from your class into a portable object and then rewriting it in order to sever any reference pointers.
Note: The passed in class and any other classes it exposes as properties must be marked <Serializable()> in order to use BinaryFormatter.Serialize
If you want to make your own class expose the clonable method itself, you can add the method and implement the ICloneable interface like this:
<Serializable()>
Public Class MyClass : Implements ICloneable
'NOTE - The Account class must also be Serializable
Public Property PersonAccount as Account
Public Property FirstName As String
Function Clone(ByRef orig As MyClass) As MyClass Implements ICloneable.Clone
' Don't serialize a null object, simply return the default for that object
If (Object.ReferenceEquals(orig, Nothing)) Then Return Nothing
Dim formatter As New BinaryFormatter()
Dim stream As New MemoryStream()
formatter.Serialize(stream, orig)
stream.Seek(0, SeekOrigin.Begin)
Return CType(formatter.Deserialize(stream), T)
End Function
End Class
Note: Be aware ICloneable comes with it's share of controversies as it does not indicate to the caller if it is performing a deep or shallow clone. In reality, you don't need the interface to be able to add the method to your class.
(As an aside, I probably would name your class something other than "Class").
If you wanted to do it all by hand you would need to follow steps like:
Ensure that your Field class also implements a deep copy Clone() method. If you haven't done this already, then this would likely involve its Clone() method creating a new object of type Field and then populating each of its properties based on the current object. If your Field class has properties which are other classes/complex types (e.g. classes you have created yourself) then they should also implement Clone() and you should call Clone() on them to create new deep copies
In your Clone() method for the class you would create a new object of type [Class], e.g. by calling its constructor
Set the Name property of the new object to the Name property of your current object
Create a new List(Of Field), let's call it listA for the sake of example
Iterate over your current list and assign a clone of each list item to listA. For example:
For Each item in _ListOfFields
listA.Add(item.Clone())
End
After that you can assign your new list (listA) to the object you have created in the Clone() method
There is an alternative (probably better) by-hand approach that is in VB.NET described here.
If you wanted to cheat a bit then you could just serialize your existing object and then deserialize it into a new object like the technique here
I would say the serialize then deserialize technique is the "easiest" one.
Related
I have a class we will call MyClass. Inside I have some deserialization code that I want to be able to call from within the object. Is there a cleaner way to assign the values associated with the class from within the class.
Here is what I am doing now
Public Class MyClass
Public Property Prop1 as New String(String.empty)
Public Property Prop2 as New String(String.empty)
Public Property Prop3 as Boolean = False
Public Sub LoadXML(ByVal XMLText as String)
Dim MyTemp as New MyClass
MyTemp = CType(DeSerialize(XMLText, MyTemp.Type), MyClass) 'this returns an object
Me.Prop1 = MyTemp.Prop1
Me.Prop2 = MyTemp.Prop2
Me.Prop3 = MyTemp.Prop3
End Sub
End Class
I cannot assign the result of MyTemp to Me (which ostensibly represent the same object type) but I can assign all of the properties of MyTemp to the properties of Me. Since my actual class is much more complicated (I used primitives in the example - but in reality is a pretty large class with properties that are many other classes) I wondered if there is a better way to assign the value of MyTemp to the instance of the class.
You probably want to create a Shared function that will return an instance of MyClass in lieu of a constructor.
Public Shared Function LoadXML(ByVal XMLText As String) As MyClass1
Dim MyTemp As New MyClass1
MyTemp = CType(DeSerialize(XMLText, MyTemp.GetType()), MyClass1)
Return MyTemp
End Function
You can run this to create the initial instance of the class, so instead of calling new you would call MyClass.LoadXML(xmlString).
You would probably be a lot better off to declare LoadXML as a shared function that just returned the deserialized object. That way, instead of
dim myClassInstance = new MyClass();
myClassInstance.LoadXML(...)
You would do
dim myClassInstance = MyClass.LoadXML(...);
And you wouldn't need to do all that property copying.
(sorry, VB is rusty)
I have an ArrayList defined in Class A. Then I want to build this array in Class B and use it in Class A.
I defined the ArrayList as:
Public arrayList As ArrayList
Then, in Class B I do:
Dim trLogkEmpty As New A
'Loop with strEspece definition
trLogkEmpty.arrayList.Add(strEspece)
'End Loop
The program throws me this error:
NullReferenceException
I don't know why, because strEspece has never become null (I tested it). I don't know if there is another reason.
Also, when I loop through the arrayList elements in Class A, I get again NullReferenceException. This is the loop code:
For Each logkNull In Me.arrayElemWithLogkEmpty
Console.WriteLine(logkNull)
Next
I don't know what happens with the first exception, but the code runs "correctly". In the second exception I guess that is something like I'm loosing the elements values of the array. I don't know how to solve it...any help? I accept different ways to solve it!
You are making two of the same mistake. A NullReferenceException means that you are attempting to access a property or method on an object that hasn't been instantiated yet. You are attempting to access both A and A.arrayList without first creating new instances of them.
So, instead of just:
trLogkEmpty.arrayList.Add(strEspece)
You should have:
Dim trLogkEmpty As New A()
trLogkEmpty.arrayList = New ArrayList()
trLogkEmpty.arrayList.Add(strEspece)
However, I must insist that you avoid ArrayList, and also that you avoid instantiating a public member of a class from outside that class. I would suggest using a strongly-typed collection class such as List(Of T), and having a read-only property in A's take care of its instantiation and visibility so the collection (not its contents) can't be modified outside of A:
Public Class A
Private _myList As IList(Of String)
Public ReadOnly Property MyList As IList(Of String)
Get
If _myList Is Nothing Then
_myList = New List(Of String)
End If
Return _myList
End Get
End Property
End Class
And now you have:
Dim trLogkEmpty As New A()
trLogkEmpty.MyList.Add(strEspece)
You're probably going to need to keep your instance of A around, so class B should probably look somewhat like:
Public Class B
Private _a As A
Public Sub New()
_a = New A()
End Sub
' ... your methods that use _a.MyList
End Class
I got it finally. When I initialised the array in class 'A' I forget to create an instance of ArrayList class, specifically, I forget to put New:
Public arrayElemWithLogkEmpty As New ArrayList
So, partly, #Blackwood was right!
Thank you all and forgive me for my basic knowledge about vb.net.
I have a set of classes, whose properties have data annotations on them. Some of these class properties are of primitive types (and by primitive, I also mean types such as string, double, datetime etc), while others are properties of a custom type.
I would like to be able to iterate through the properties of a class and the properties of the nested objects and pull out the attributes of each property. I’ve played around with reflection and my code works fine, if the class under consideration has only one property of a custom type.
However when a class has multiple properties of a custom type and each of those properties have other custom types, I am completely lost on how I’d keep track of the objects/properties that have already been visited.
This is where I have got so far. I have seen a lot of examples on the forum, but they all have a simple nested class, where there is a maximum of one custom type per class.
Below is a sample of what I am trying to get done:
Public Class Claim
<Required()>
<StringLength(5)>
Public Property ClaimNumber As String
<Required()>
Public Property Patient As Patient
<Required()>
Public Property Invoice As Invoice
End Class
Public Class Patient
<Required()>
<StringLength(5)>
Public Property MedicareNumber As String
<Required()>
Public Property Name As String
<Required()>
Public Property Address As Address
End Class
Public Class Address
Public Property Suburb As String
Public Property City As String
End Class
Public Class Invoice
<Required()>
Public Property InvoiceNumber As String
<Required()>
Public Property Procedure As String
End Class
Public Shared Function Validate(ByVal ObjectToValidate As Object) As List(Of String)
Dim ErrorList As New List(Of String)
If ObjectToValidate IsNot Nothing Then
Dim Properties() As PropertyInfo = ObjectToValidate.GetType().GetProperties()
For Each ClassProperty As PropertyInfo In Properties
Select Case ClassProperty.PropertyType.FullName.Split(".")(0)
Case "System"
Dim attributes() As ValidationAttribute = ClassProperty.GetCustomAttributes(GetType(ValidationAttribute), False)
For Each Attribute As ValidationAttribute In attributes
If Not Attribute.IsValid(ClassProperty.GetValue(ObjectToValidate, Nothing)) Then
ErrorList.Add("Attribute Error Message")
End If
Next
Case Else
Validate(ClassProperty.GetValue(ObjectToValidate, Nothing))
**** ‘At this point I need a mechanism to keep track of the parent of ClassProperty and also mark ClassProperty as visited, so that I am able to iterate through the other properties of the parent (ObjectToValidate), without revisiting ClassProperty again.**
End Select
Next
End If
Return Nothing
End Function
The most straightforward (and probably easiest) way to approach this is to keep a Dictionary of class property attributes keyed by class name.
If I were approaching this, I would probably create a class to hold the property attributes:
Public Class PropertyAttribute
Public PropertyName As String
Public PropertyTypeName As String
Public Required As Boolean
Public StringLength As Integer
End Class
Then create a class to hold information about each class' properties:
Public Class ClassAttributes
Public ClassName As String
' You could also use a dictionary here to key properties by name
Public PropertyAttributes As New List(Of PropertyAttribute)
End Class
Finally, create a dictionary of ClassAttributes to keep track of which custom classes you have already processed:
Public ProcessedClasses As New Dictonary(Of String, ClassAttributes)
The key for the dictionary is the classname.
When you are processing the attributes through reflection, if the property type is custom, check the dictionary for the existence of the class. If it is there, you don't have to process it.
If it is not there, add a new instance to the dictionary immediately (so that nested objects of the same type are safely handled) and then process the attributes of the class.
I have a webservice that is wrapped up by a data access object and is accessed by many different UI controls.
The proxy objects look something like this:
Public Class WebProxyObject1
' Common properties, there are about 10 of these
Public Name As String
Public Address As String
' Specialized properties there are about 20 of these
Public count As Integer
End Class
The DAL layers look something like this:
Public Class DataAccessObject
Implements IDataAccessObject
' These are called in MANY, MANY, MANY locations
Public Function GetObject(ByVal name As String) As WebProxyObject1 Implements IDataAccessObject.GetObject
' Makes call to a webservice
Return New WebProxyObject1
End Function
Public Function ListObjects() As System.Collections.Generic.List(Of WebProxyObject1) Implements IDataAccessObject.ListObjects
' Makes call to a webservice
Dim list As New List(Of WebProxyObject1)
Return list
End Function
End Class
Now, I need to add a 2nd webservice to the mix. The goal is to reuse the UI controls that are currently coded to use the Proxy Objects that come from the first webservice. There are about 10 common properties and about 20 that are different. To add the 2nd webservice, I'd create a 2nd DAL object that implements the same interface. The problem is that it currently returns the proxies from the first webservice.
My thought on how to solve this is to extract an interface from each of the proxy objects and mash them together. Then implement the new interface on both proxy objects. That will create a huge class/interface where some properties aren't used. Then have the DAL return the interface.
The problem that I'm facing isn't a real bug or an issue, but extracting the 2 interfaces and smashing them together just feels kind of wrong. I think it would technically work, but it kind of smells. Is there a better idea?
The resulting interface would look like this:
Public Interface IProxyObject
' Common
Property Name() As String
Property Address() As String
' Specialized
Property Count() As Integer
Property Foo() As Integer
End Interface
Create a base class for your WebProxyObjects to inherit from.
Public MustInherit Class WebProxyObjectBase
' Common properties
Public Property Name As String
Public Property Address As String
End Class
Next create your two WebProxyObjects:
Public Class WebProxyObject1
Inherits From WebProxyObjectBase
' Specialized properties
Public Property count As Integer
End Class
Public Class WebProxyObject2
Inherits From WebProxyObjectBase
' Specialized properties
Public Property foo As Integer
End Class
Next have your DAL return the Base Class:
Public Class DataAccessObject
Implements IDataAccessObject
' These are called in MANY, MANY, MANY locations
Public Function GetObject(ByVal name As String) As WebProxyObjectBase Implements IDataAccessObject.GetObject
' Makes call to a webservice
Return New WebProxyObjectBase
End Function
Public Function ListObjects() As System.Collections.Generic.List(Of WebProxyObjectBase) Implements IDataAccessObject.ListObjects
' Makes call to a webservice
Dim list As New List(Of WebProxyObjectBase)
Return list
End Function
End Class
Then when calling your DataAccessObject you'll be able to ctype the return to the proper class:
Dim DAO as New DataAccessObject
Dim Pxy1 as WebProxyObject1 = TryCast(DAO.GetObject("BOB"), WebProxyObject1)
If Pxy1 IsNot Nothing Then
'Do stuff with proxy
End If
I am stuck with a problem about generic classes. I am confused how I call the constructor with parameters.
My interface:
Public Interface IDBObject
Sub [Get](ByRef DataRow As DataRow)
Property UIN() As Integer
End Interface
My Child Class:
Public Class User
Implements IDBObject
Public Sub [Get](ByRef DataRow As System.Data.DataRow) Implements IDBObject.Get
End Sub
Public Property UIN() As Integer Implements IDBObject.UIN
Get
End Get
Set(ByVal value As Integer)
End Set
End Property
End Class
My Next Class:
Public Class Users
Inherits DBLayer(Of User)
#Region " Standard Methods "
#End Region
End Class
My DBObject Class:
Public Class DBLayer(Of DBObject As {New, IDBObject})
Public Shared Function GetData() As List(Of DBObject)
Dim QueryString As String = "SELECT * ***;"
Dim Dataset As DataSet = New DataSet()
Dim DataList As List(Of DBObject) = New List(Of DBObject)
Try
Dataset = Query(QueryString)
For Each DataRow As DataRow In Dataset.Tables(0).Rows
**DataList.Add(New DBObject(DataRow))**
Next
Catch ex As Exception
DataList = Nothing
End Try
Return DataList
End Function
End Class
I get error in the starred area of the DBLayer Object.
What might be the possible reason? what can I do to fix it?
I even want to add New(byval someval as datatype) in IDBObject interface for overloading construction. but it also gives an error? how can i do it?
Adding
Sub New(ByVal DataRow As DataRow) in IDBObject producess following error
'Sub New' cannot be declared in an interface.
Error Produced in DBLayer Object
line: DataList.Add(New DBObject(DataRow))
Msg: Arguments cannot be passed to a 'New' used on a type parameter.
The problem is with the New constraint. It only promises a parameter-less constructor, the compiler cannot deduce that the DbObject type parameter may have a constructor that takes a DataRow as an argument.
You could perhaps extend the IDbObject interface with a property that gets/sets a DataRow. A class factory would be helpful.
Its not as simple as it being confused about what you are referring to? You've got the line "For Each DataRow As DataRow" which seems like a recipe for confusion and it may be that when you are referring to DataRow in your constructor it isn't using your loop variable but instead the datatype "DataRow". Even if that's not the problem you probably want to change your loop variable to something less ambiguous.
What is the actual error that you get?
Also for the last part of adding extra constructors what error do you get? If you are adding somethign to the interface are you adding the extra constructor to everythign that implements the interface?
What if you use an abstract class instead of the interface IDBObject? You should then inherit instead of implement.
The abstract class could define the needed constructor.
for the constructor, that is because you can't define a constructor in an interface.
EDIT:
I see what you are trying to do now. you want to create a new instance of the generic type that your DBLayer is defined with. Your problem is going to be that you can't specify that your implementations of the interface need to have a particular constructor. You might be better having a method on your interface which accepts a DataRow and uses this to initialise the IDBObject, then you could create an instance of the type DBObject and then call the method which accepts the DataRow with which to intialize your instance.
Or you could require that your DBLayer class takes a factory class that can produce an object of type DBObject given a DataRow and you could call this method of the factory in place of calling
For Each rowAs DataRow In Dataset.Tables(0).Rows
//DataList.Add(New DBObject(row))
DataList.Add(m_factory.CreateObject(row));
Next
EDIT 2:
the easiest approach will be to extend your interface to allow you to set the DataRow and call the method in the loop:
For Each row As DataRow In Dataset.Tables(0).Rows
//DataList.Add(New DBObject(row))
Dim newElement As IDBObject = CType(Activator.CreateInstance(GetType(DBObject)), IDBObject)
//you'll need to add this SetDataRow method the the IDBObject interface
newElement.SetDataRow(row)
DataList.Add(newElement)
Next