How can I control the element names of serialized subclasses? - vb.net

Let's say I have the following class structure (simplified from my real-world problem):
Public Class PC_People_Container
Private _people_list As New List(Of PL_Person)
Public Sub New()
End Sub
Public Sub Add(ByVal item As PL_Person)
_people_list.Add(item)
End Sub
Public Property PeopleList As List(Of PL_Person)
Get
Return _people_list
End Get
Set(ByVal value As List(Of PL_Person))
_people_list = value
End Set
End Property
End Class
Public Class PL_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
Private _Contacts As ContactObject
Public Property Contacts As ContactObject
Get
Return _Contacts
End Get
Set(ByVal value As ContactObject)
_Contacts = value
End Set
End Property
Public Sub New()
End Sub
End Class
Public Class ContactObject
Public Property PhoneNumber As String
Public Property EmailAddress As String
Public Sub New()
End Sub
End Class
If I were to serialize this, I'd get the default assigned node names in my XML. That means my root is named PC_People_Container and each person in the list is marked up as PL_Person. I know I can change the root node using <XmlRoot(ElementName:="PeopleContainer")>. The trouble is doing that for the subclasses. I can't use the <XmlRoot> tag on PL_Person class because there can't be two root elements, and IntelliSense throws a fit when I try to use the <XmlElement> tag on a class like I would on a property. Is it even possible to control what those subclasses are named when they're serialized as child nodes?

PL_Person and ContactObject are not subclasses as you call them, they are merely property types.
This makes your question confusing because it suggests you may have a problem with inheritance (subclasses are classes that inherit from some base class) when in fact you just want your property elements to be named differently.
You should decorate your properties (not classes) with <XmlElement> to specify custom name:
<XmlElement("Persons", GetType(PL_Person))>
Public Property PeopleList As List(Of PL_Person)
As an afterthought, I would definitely not recommend calling your classes using such an awkward convention. In .NET, you should not use any prefixes or underscores in class names. Just call it Person.

Related

Retrieve object name

I need to retrieve the name of an instanced object (not the type name...)
I have seen that the GetProperties() function gets the child properties name but i need the name of the current object
Public Class Class1
Private mValore As String
Public Property Valore As String
Get
Return mValore
End Get
Set(value As String)
mValore = value
End Set
End Property
End Class
Public Class Class2
Private mMickey As new Class1
Public Property Mickey As Class1
Get
Return mMickey
End Get
Set(value As Class1)
mMickey = value
End Set
End Property
End Class
I need to obtain inside Class1 the name of instanced object in Class2: "Mickey"
Is it possible ?
Thanks in advice for all that will answer me.
As mentioned by Hans Passant, objects don't have names.
So if you really need names, you may introduce them, as a property or field. You may employ CallerMemberNameAttribute to automatically pass the caller name to e.g. constructor.
Another thing, objects might be created outside Class2, indeed in the Mickey ... Set setter you are assigning mMickey field to an object from somewhere outside, so the object might have a different name. I would prefer to create a copy of object instead of just assignment, then we can assign any name to it and it will not collide with the previous name. An example could be:
Imports System.Runtime.CompilerServices
Public Class Class1
Private mValore As String
Public ReadOnly Name As String
Public Sub New(mValore As String, <CallerMemberName> Optional callerMemberName As String = Nothing)
Me.mValore = mValore
Me.Name = callerMemberName
End Sub
Public ReadOnly Property Valore As String
Get
Return mValore
End Get
End Property
End Class
Public Class Class2
Private mMickey As Class1
Public Property Mickey As Class1
Get
Return mMickey
End Get
Set(value As Class1)
mMickey = New Class1(mValore:=value.Valore)
End Set
End Property
End Class
If Class2 only has one property, you can just get the only property's name
Public Class Class1
Public ReadOnly Property Valore As String
Get
Return GetType(Class2).GetProperties().Single().Name
End Get
End Property
End Class
Public Class Class2
Public Property Mickey As Class1
End Class
Or if it has multiple properties, you can just get the first property's name
Public Class Class1
Public ReadOnly Property Valore As String
Get
Return GetType(Class2).GetProperties().First().Name
End Get
End Property
End Class
Public Class Class2
Public Property Mickey As Class1
Public Property Mouse As String
End Class
That returns the first property in order in which the properties are defined. So if the order is changed, it breaks.
Surely there must be more qualifying information to lead us to a solution. Can I make the assumption that you are only interested in the name of the property whose type is Class1? Then you can also filter on the property's type
Public Class Class1
Public ReadOnly Property Valore As String
Get
Return GetType(Class2).GetProperties().Where(Function(pi) pi.PropertyType Is GetType(Class1)).Single().Name
End Get
End Property
End Class
Public Class Class2
Public Property Mouse As String
Public Property Mickey As Class1
End Class
I think this is exactly what you're looking for. But if not, let me know and we can work it out.

How do I get a superclass to reference a property in a subclass when the subclass invokes the superclass' method?

The superclass:
Public MustInherit Class Product
Friend _shortName as String = Nothing
Public ReadOnly Property Name as String
Get
return _shortName
End Get
End Property
End Class
The Sub class
Public Class MyProduct : Inherits Product
Friend Shadows _shortName as String = "MyProd"
End Class
So, in the immediate console when I'm debugging, I do:
Dim product as new MyProduct
product.Name ' => Nothing
product.Name should be "MyProd" - but it isn't. How do I set this up correctly, so that the the property defined in the superclass accesses the field defined in the subclass?
There is no way for the base class to access the shadowed version of the field. Shadows should be avoided unless it is absolutely necessary. For something like this, you should just change the value of the base field from the derived class. There is no need to shadow it:
Public Class MyProduct : Inherits Product
Public Sub New()
_shortName = "MyProd"
End Sub
End Class
It's worth mentioning that, unless you really need it to be scoped as Friend, the _shortName field in the base class should be scoped as Protected.
Although, in this example, it looks like you probably want all derived classes to provide the name. In that case, there are two ways to accomplish that. You could require the name as a parameter in the base class' constructor:
Public MustInherit Class Product
Public Sub New(shortName As String)
_shortName = shortName
End Sub
Friend _shortName As String = Nothing
Public ReadOnly Property Name As String
Get
Return _shortName
End Get
End Property
End Class
Public Class MyProduct : Inherits Product
Public Sub New()
MyBase.New("MyProd")
End Sub
End Class
In this case, the _shortName doesn't even need to be Friend or Protected. It should ideally be scoped as Private.
Or, you could simply declare the property as MustOverride:
Public MustInherit Class Product
Public MustOverride ReadOnly Property Name As String
End Class
Public Class MyProduct : Inherits Product
Public Overrides ReadOnly Property Name As String
Get
Return "MyProd"
End Get
End Property
End Class

Get a Field Object, not FieldInfo, from a VB Class Instance

I am trying to iterate through objects (fields) in a class and invoke a method on each object. Each object is of a different type. Here is the parent class:
Public Class MySettings
Public IdentifyByFacType As RadioButtonSetting
Public WtrFacTypes As ListSetting
Public OilFacTypes As ListSetting
Public GroupByRef As CheckboxSetting
Public GroupRefAttr As TxtboxSetting
End Class
Here is part of one of the sub-object classes:
<Serializable>
Public Class TxtboxSetting
<XmlIgnore()>
Public MyControl As Windows.Forms.TextBox
<XmlIgnore()>
Public DefaultSetting As String
Private _SavedSetting As String
Public Property SavedSetting As String
Get
Return _SavedSetting
End Get
Set(value As String)
_SavedSetting = value
CurrentValue = value
End Set
End Property
Public Sub New()
End Sub
Public Sub New(DefaultSetting As String, MyControl As Windows.Forms.TextBox)
Me.DefaultSetting = DefaultSetting
Me.MyControl = MyControl
End Sub
Public Sub RestoreDefault()
CurrentValue = DefaultSetting
End Sub
End Class
All of the sub-objects of the MySettings class, like GroupRefAttr for example, have the same methods and properties, but the internal code is different.
So I will have several classes like the MySettings class, and each one will have different sub-objects. Given an instance of such a class, I want to automatically iterate through the fields and call a method RestoreDefault on each one. I don't want to have to know what objects exist in the MySettings class. Rather, knowing that they all have the RestoreDefaultmethod, I want simply call the method on each object.
Despite much searching, I have not found a way to do this. With reflection, I can only get this far:
Dim Opts as New MySettings
For Each var In Opts.GetType.GetFields
Dim RestoreDefault As System.Reflection.MethodInfo = var.FieldType.GetMethod("RestoreDefault")
RestoreDefault.Invoke(Opts, Nothing)
Next
However, in the line RestoreDefault.Invoke(Opts, Nothing), I can't just pass in Opts, as I am dealing with a field in Opts, not Opts itself. A statement like this would work: RestoreDefault.Invoke(Opts.GroupRefAttr, Nothing), but that requires me to know the objects in the MySettings class ahead of time, and that defeats the purpose. Is there a way to grab field instance objects at runtime and pull this off?
When you invoke the RestoreDefault method you need to invoke it on the setting (i.e., the value of the field), not the class containing the setting. Changing your code to this should fix your problem:
Dim Opts as New MySettings
For Each var In Opts.GetType.GetFields
Dim setting As Object = var.GetValue(Opts)
Dim RestoreDefault As System.Reflection.MethodInfo = var.FieldType.GetMethod("RestoreDefault")
RestoreDefault.Invoke(setting, Nothing)
Next
However, if you introduce either a base class or an interface you should be able to get rid of some or all of the reflection. The container setting class can have a collection of settings that each have a shared base class or interface with a RestoreDefault method. The container setting class will then call this method through the base class or interface without having to use reflection.
The base class:
Public MustInherit Class BaseSetting
Public MustOverride Sub RestoreDefault
End Class
A specific settings class:
Public Class TxtboxSetting
Inherits BaseSetting
Public Overrides Sub RestoreDefault()
' Specific implementation
End Sub
End Class
On any class deriving from BaseSetting you can now call the RestoreDefault method without having to use reflection.
However, considering your design you might still want to use reflection to get the settings containd in the MySettings class. You can do it like this:
Dim settings = From fieldInfo in Opts.GetType.GetFields
Where GetType(BaseSetting).IsAssignableFrom(fieldInfo.FieldType)
Select DirectCast(fieldInfo.GetValue(Opts), BaseSetting)
For Each setting In settings
setting.RestoreDefault()
Next
Reflection is used to find all the fields deriving from BaseSetting and then RestoreDefault is called on each field. This method does not suffer from the "magic string" problem where your code depends on the name of the RestoreDefault method represented in a string.
(Also, calling the MySettings class the parent is a bit misleading because there is nothing inheriting from MySettings. Instead this class contains other settings.)
All of the sub-objects of the MySettings class, like GroupRefAttr for example, have the same methods and properties, but the internal code is different.
In that case, the sub-object types should be defined such that they implement a common interface that demands these same methods and properties exist. For now, I'll name that interface IControlSetting. Then, your For loop looks something like this:
Dim Opts as New MySettings
For Each var In Opts.GetType.GetFields
Dim setting As IControlSetting = TryCast(var.GetValue(Opts), IControlSetting)
If setting Is Nothing Then Continue
setting.RestoreDefault()
Next
Additionally, I'd change your MySettings type to encapsulate a dictionary or IControlSetting objects. Then you can just iterate the dictionary to check each of the objects, rather than needing reflection. That might look like this:
Public Class MySettings
Private allSettings As Dictionary(Of String, IControlSetting)
Public Sub New()
allSettings = new Dictionary(Of String, IControlSetting)()
allSettings.Add("IdentifyByFacType", New RadioButtonSetting())
allSettings.Add("WtrFacTypes", New ListSetting())
allSettings.Add("OilFacTypes", New ListSetting())
'...
End Sub
Public Property IdentifyByFacType As RadioButtonSetting
Get
Return DirectCast(allSettings("IdentifyByFacType"), RadioButtonSetting)
End Get
'The setters may be optional, depending on how you expect to use these
Set(ByVal value As RadioButtonSetting)
allSettings("IdentifyByFacType") = value
End Set
End Property
Public Property WtrFacTypes As ListSetting
Get
Return DirectCast(allSettings("WtrFacTypes"), RadioButtonSetting)
End Get
Set(ByVal value As ListSetting)
allSettings("WtrFacTypes") = value
End Set
End Property
Public Property OilFacTypes As ListSetting
Get
Return DirectCast(allSettings("OilFacTypes"), RadioButtonSetting)
End Get
Set(ByVal value As ListSetting)
allSettings("OilFacTypes") = value
End Set
End Property
'...
Public Sub RestoreAllDefaults()
For Each setting As KeyValuePair(Of String, IControlSetting) In allSettings
setting.Value.RestoreDefault()
Next setting
End Sub
End Class

VB.Net Create Database table from class property

I'm trying to create an inheritable class(OF t) in vb.net that I will pass it a class of objects. Inside the class of objects I want to use the class properties to create a corresponding database table. Like below
Public Class SampleClass
#Region "Properties"
Private newPropertyValue As String
Public Property NewProperty() As String
Get
Return newPropertyValue
End Get
Set(ByVal value As String)
newPropertyValue = value
End Set
End Property
#End Region
Public Sub New()
End Sub
End Class
I'm new to vb.net so I don't know my way around exactly.
I was looking into class attributes for this action but they do not fully make sense to me yet. Thanks in advance.
You will want to get well versed on something called Code First. This should get you started.

Optional Readonly Property in VB.Net Interface

I am trying to develop a simple interface for allowing quick lists to be generated from classes. Basically, the interface needs to return an ID and a Name. However, some classes have a calculated name property which is read only, others just use a read/write name property. Basically, all I care is that it has a getter, it does not matter if the property has a setter. How can I write this interface to handle either or without throwing compile errors?
I have read this question and didn't really follow it, maybe I am just dense. If so, please show me the error of my ways :)
Looks like the answer from the other question will work: here's a sample:
Public Interface IReadOnly
ReadOnly Property Name() As String
End Interface
Public Interface IReadWrite
Inherits IReadOnly
Overloads Property Name() As String
End Interface
Public Class ReadOnlyClass
Implements IReadOnly
Private _Name
Public ReadOnly Property Name() As String Implements IReadOnly.Name
Get
Return _Name
End Get
End Property
End Class
Public Class ReadWriteClass
Implements IReadWrite
Private ReadOnly Property ReadOnly_Name() As String Implements IReadOnly.Name
Get
Return Name
End Get
End Property
Private _Name As String
Public Overloads Property Name() As String Implements IReadWrite.Name
Get
Return _Name
End Get
Set(ByVal value As String)
_Name = value
End Set
End Property
End Class
The above approach will actually result in classes that implement IReadWrite also implementing IReadOnly--so you'll actually need to downcast to IReadWrite in order to set the property.
Another approach, which avoids that issue but requires a little more logic in the implementing classes and their caller's is something like:
Public Interface ISometimesWritable
Property Name() As String
ReadOnly Property AllowNameEdit() As Boolean
End Interface
Public Class ReadOnlyClass
Implements ISometimesWritable
Public ReadOnly Property AllowNameEdit() As Boolean Implements ISometimesWritable.AllowNameEdit
Get
Return False
End Get
End Property
Private _Name As String
Public Property Name() As String Implements ISometimesWritable.Name
Get
Return _Name
End Get
Set(ByVal value As String)
Throw New NotSupportedException("Name cannot be set when AllowNameEdit is False")
End Set
End Property
End Class
Public Class ReadWriteClass
Implements ISometimesWritable
Public ReadOnly Property AllowNameEdit() As Boolean Implements ISometimesWritable.AllowNameEdit
Get
Return True
End Get
End Property
Private _Name As String
Public Property Name() As String Implements ISometimesWritable.Name
Get
Return _Name
End Get
Set(ByVal value As String)
_Name = value
End Set
End Property
End Class
Update: To answer the question about downcasting; "downcasting" is a term used to describe casting an object from a superclass, interface, or abstract base class Type into a more concrete Type.
For example, the first example above defines two interfaces: IReadOnly and IReadWrite. You'll notice that IReadWrite implements IReadOnly, which means that you can make both IReadWrite and IReadOnly calls to objects which implement IReadWrite.
Since IReadWrite implements IReadOnly, IReadWrite is said to be a "sub-class" of IReadOnly (although "sub-class" is more accurately used to describe a class which inherits a base class, rather then implements an interface--for the sake of simplicity they are very nearly the same concept). If IReadWrite is a sub-class of IReadOnly, then the inverse is true--IReadOnly is a super-class of IReadWrite.
For example, I can describe an instance of ReadWriteClass as an implementation of either interface:
Public Sub SomeMethod()
dim readOnlyInstance as IReadOnly = new ReadWriteClass()
Console.WriteLine(readOnlyInstance.Name)
' The following line won't compile, since we're communicating with ReadWriteClass as an instance of IReadOnly
'readOnlyInstance.Name = "Santa Clause"
' Here we downcast the variable to reference it by it's other interface, IReadWrite
dim readWriteInstance = DirectCast(readOnlyInstance, IReadWrite)
' Now we can both Get and Set the value of Name
readWriteInstance.Name = "John Doe"
Console.WriteLine(readWriteInstance.Name)
' Note that in the above example we created *one* instance of ReadWriteClass
' and have provided two variables / references to the same underlying object.
Console.WriteLine(readOnlyInstance.Name) ' <-- note that this should return "John Doe"
End Sub