How to add a "sub property" to a class property - vb.net

If, in code, I wanted to do something like the following, what would my class definition need to look like? (Keep in mind the fruit/language thing is just an example)
dim myfruit as new fruit()
myfruit.name = "apple"
myfruit.name.spanish = "manzana"
Here is the class I have, just not sure how to add the "sub property".
Public Class Fruit
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
End Class

In general, for you to have a "sub property", you'd need to make your Property a class itself. This would mean the subproperty is actually a property on the class exposed by the top level property.
Effectively, you'd change the name property from a string to a "Translations" class or similar, ie:
Public Class Fruit
Public Property Name As New Translations
End Class
Public Class Translations
Public Property Primary As String
public Property Spanish As String
End Class
However, this will likely break the code you're displaying, as the second line would need to have a different syntax, ie:
myfruit.Name.Primary = "green"
myfruit.Name.Spanish = "verde"
However, if the goal here is to just handle translation of your user interface, there are other options. For details, see Introduction to International Applications Based on the .NET Framework on MSDN.

I initially thought Reed´s answer was what I was after. In my application, I wanted to use the "sub-property" to set a property on a Form Label. (I was trying to emit only the Label properties I wanted available to a Custom Control.)
I tried this:
Public Class Fruit
Private _name As New Translations
Public Property Name As Translations
Get
Return _name
End Get
Set(value As Translations)
_name = value
_PrimaryCaps = _name.Primary.ToUpper
End Set
End Property
'Private variable is automatically added for unexpanded property
Public Property PrimaryCaps As String
End Class
Public Class Translations
Public Property Primary As String
Public Property Spanish As String
End Class
Then
Dim myFruit As New Fruit
myFruit.Name.Primary = "Apple"
myFruit.Name.Spanish = "Manzana"
Dim primaryCaps As String = myFruit.PrimaryCaps
Weirdly - to me at least - this doesn't work; myFruit.PrimaryCaps returns nothing rather than the hoped-for "APPLE". It appears that the Set for Name is never executed. (Placing the _PrimaryCaps assignment above the Get Return does work, however.)
(I realize that a PrimaryCaps property could be added to the Translations class but, again, this doesn't help if you're wanting to set a foreign variable from within an instance of Fruit.)
I don't know if this is "by-design", whether I've simply misunderstood the intended functionality or what. One thing I did alight on after further research was that this structure isn't very common at all in .NET; for example setting a control's size is done as follows:
oControl.Size = New Drawing.Size(20, 15)
rather than simply setting, say, the Width property directly:
oControl.Size.Width = 20
(The latter won't compile: "Expression is a value and therefore cannot be the target of an assignment.")
If anyone has any more insight than I on this, I'd love to hear it. I know this could simply be done by using an instance of Fruit, for example, but that's not the point.

Related

Access a class property only from a certain class

Suppose I have the following class:
Public Class lbMenu
Inherits Form
...
Public Sub AddItem(header As String)
Dim item As New lbMenuItem()
item.Owner = Me
item.Header = header
Controls.Add(item)
End Sub
...
End Class
and also the following class:
Public Class lbMenuItem
Inherits Control
Private _Owner As lbMenu
Public Property Owner As lbMenu
Get
Return _Owner
End Get
Set(value As lbMenu)
_Owner = value
End Set
End Property
Private _Header As String
Public Property Header As String
Get
Return _Header
End Get
Set(value As String)
_Header = value
End Set
End Property
...
End Class
As you can see, I access the Owner property of lbMenuItem from the class lbMenu.
What I want is that nobody else can access the Owner property, or maybe just to read the value of the property. I think there is no way to do that but maybe there is a solution to reach a similar situation.
Any suggestions?
Edit:
I already considered passing the owner as a parameter in the constructor, but I want that anyone can access the lbMenuItem class. Anyone can create a new instance of the class, read and write many properties, etc. The only thing I don't want is that someone can write the Owner property. Someone can create a new instance of the class without having to pass an ownerMenu parameter to the New method (he can't know what ownerMenu is for). For the same reason, lbMenuItem can't be a nested class of lbMenu.
I'm trying to get the same result as with ContextMenuStrip and ToolStripMenuItem. ToolStripMenuItem has an Owner property (which in this case has read/write access) and an OwnerItem which is read-only.
So, is there a way to make the Owner property read-only but writable from the lbMenu class?
You could just have the Owner property private or read-only if you want to allow reading its value and set it in the constructor.
Edit:
Someone can create a new instance of the class without having to pass
an ownerMenu parameter to the New method
Well, you can have two constructors, one that accepts a lbMenu parameter, and one without. So, your code would look something like this:
Public Class lbMenu
Public Sub AddItem(header As String)
Dim item As New lbMenuItem(Me)
item.Header = header
Controls.Add(item)
End Sub
End Class
Public Class lbMenuItem
' Use this instead if you don't want the property to be accessible at all.
'Private Property Owner As lbMenu
Public ReadOnly Property Owner As lbMenu
Public Property Header As String
' Allow creating an instance of the class without having to pass/set the owner.
Public Sub New()
End Sub
Public Sub New(ownerMenu As lbMenu)
Owner = ownerMenu
End Sub
End Class
One last recommendation:
Based on the names of your classes, you might consider having a parent/child relationship between them (i.e., the lbMenuItem class would be a nested class within lbMenu). This will give you the advantage of being able to access the private members of lbMenu from a lbMenuItem instance.
Hope that helps.
Addressing the new points in your edit:
ToolStripMenuItem has an Owner property ... and an OwnerItem which is read-only.
That's exactly what I proposed (i.e., to use a read-only property). Note that the OwnerItem property isn't exposed as writable to any other class.
So, is there a way to make the Owner property read-only but writable
from the lbMenu class?
No, that's not possible. A read-only property is read-only, period. You can't expose its setter to a specific class. You can, however, limit the accessibility to its setter by using the keywords Friend, Private, or Protected:
Friend Set --> will make the setter accessible (make the property writable) inside the current project and read-only outside the project.
Private Set --> will make the setter accessible from the child classes.
Protected Set --> will make the setter accessible from the derived classes.
Take a look at this question to get a better idea on how to limit the accessibility of a property setter. It's about C#, but the concept is the same.

Why are arguments renamed to RHS when implementing an Interface in VBA?

When you implement an Interface in your Class the arguments are automatically named RHS as shown on MDSN https://msdn.microsoft.com/en-us/library/office/gg264387.aspx
For example, if I create IInterface as so:
Public Property Let Value1(strValue1 As String)
End Property
Public Property Let Value2(strValue2 As String)
End Property
And implement it, the class would look like this:
Implements IInterface
Private Property Let IInterface_Value1(RHS As String)
End Property
Private Property Let IInterface_Value2(RHS As String)
End Property
It's a best practice to name your arguments in such a way as to provide some level of abstraction and make it easier to read and write code. I can actually change the arguments to whatever I want in the class after I've implemented the statements, as shown below, but my question is why does this happen? Is RHS a leftover from another language or is there a particular reason it's named like this?
Implements IInterface
Private Property Let IInterface_Value1(strValue1 As String)
End Property
Private Property Let IInterface_Value2(strValue2 As String)
End Property
The above compiles fine if I manually change it.
rhs stands for right hand side of operator = and lhs for left hand side of =. Why is this named like this here? Maybe its something which comes from c++ conventions. By the properties you have consider this code:
Dim test As IInterface
Set test = New ClassTest
test.Value1 = "rhsVal"
The new string value is actually on the right side of the = so therefor rhs.

Why is this Entity Framework association not loading lazily?

I'm using a Code First Entity Framework approach, and in my OnModelCreating function I have the following code:
With modelBuilder.Entity(Of FS_Item)()
.HasKey(Function(e) e.ItemKey)
.Property(Function(e) e.ItemRowVersion).IsConcurrencyToken()
.HasMany(Function(e) e.ItemInventories) _
.WithRequired(Function(e) e.Item).HasForeignKey(Function(e) e.ItemKey)
End With
Elsewhere I have a Web API Get implementation with some diagnostic code I'm looking at in a debugger:
Public Function GetValue(ByVal id As String) As FS_Item
GetValue = If(data.FS_Item.Where(Function(i) i.ItemNumber = id).SingleOrDefault(), New FS_Item())
Dim c = GetValue.ItemInventories.Count
End Function
I expect that c should get a non-zero value by looking up rows in the FS_Inventory view where ItemKey matches the retrieved FS_Item row's ItemKey. But I'm getting 0 even though there are matching rows. Am I calling .HasMany, .WithRequired and .HasForeignKey properly?
Note that .WithRequired is operating on the return value from the previous line whereas the other lines are operating on the With block expression.
Edit This model for FS_Item has been requested. Here it is:
Partial Public Class FS_Item
Public Property ItemNumber As String
Public Property ItemDescription As String
Public Property ItemUM As String
Public Property ItemRevision As String
Public Property MakeBuyCode As String
' Many many more properties
Public Property ItemRowVersion As Byte()
Public Property ItemKey As Integer
Private _ItemInventories As ICollection(Of FS_ItemInventory) = New HashSet(Of FS_ItemInventory)
Public Overridable Property ItemInventories As ICollection(Of FS_ItemInventory)
Get
Return _ItemInventories
End Get
Friend Set(value As ICollection(Of FS_ItemInventory))
_ItemInventories = value
End Set
End Property
End Class
Edit Learned something interesting. If I change Dim c = GetValue.ItemInventories.Count to this:
Dim c = data.FS_ItemInventory.ToList()
Dim correctCount = GetValue.ItemInventories.Count
Then correctCount gets the value of 3. It's like it understands the association between the objects, but not how to automatically query them as I'm used to coming from LINQ-to-SQL. Is EF different somehow in this regard?
Edit I have determined that I can make the associated objects load using this explicit loading code:
data.Entry(GetValue).Collection(Function(e) e.ItemInventories).Load()
What I want to understand now is what exactly determines whether an entity will load lazily or not? From all indications I can find, it should have loaded lazily. I even tried changing the declaration of ItemInventories to this, but then I got a NullReferenceException when trying to access it:
Public Overridable Property ItemInventories As ICollection(Of FS_ItemInventory)
It turns out that code which I thought was unrelated had disabled lazy loading. I have this in the constructor of FSDB:
DirectCast(Me, IObjectContextAdapter).ObjectContext.ContextOptions.ProxyCreationEnabled = False
Thanks to EF 4 - Lazy Loading Without Proxies I see that this will also disable lazy loading. The reason that code had been added was due to another error:
Type
'System.Data.Entity.DynamicProxies.FS_Item_64115A45C642902D6044AFA1AFD239E7DCB82FD000A10FE4F8DE6EA26A2AB418'
with data contract name
'FS_Item_64115A45C642902D6044AFA1AFD239E7DCB82FD000A10FE4F8DE6EA26A2AB418:http://schemas.datacontract.org/2004/07/System.Data.Entity.DynamicProxies'
is not expected. Consider using a DataContractResolver or add any
types not known statically to the list of known types - for example,
by using the KnownTypeAttribute attribute or by adding them to the
list of known types passed to DataContractSerializer.
And according to Serialization of Entity Framework objects with One to Many Relationship, the easy solution for that was to disable proxies.

How does "Overloads" work in a child class?

I have a base class and a child class and they both have the same property and I don't understand why VB wants me to use "Overloads" for the property in the child class. The difference is the child class version of the property is Shared while the parent class is basically there for structure. The properties look like this:
Public MustInherit Class Parent
Public ReadOnly Property Species As String
Get
Return "Should get species from a child."
End Get
End Property
End Class
Public Class Child
Inherits Parent
Public Shared ReadOnly Property Species As String
Get
Return "Species1"
End Get
End Property
End Class
Species is flagged in the line Public Shared ReadOnly Property Species As String in the child class with the warning message
property 'Species' shadows an overloadable member declared in the base
class 'Parent'. If you want to overload the base method, this method
must be declared 'Overloads'.
What I want to know is why does it want this to be overloaded? Overloading is typically used when different parameters are being passed into functions with the same name which is well documented, but I've found nothing explaining why overloads is suddenly suggested in a situation like this.
Note: that the code properly reports "Species1" regardless of if have the "Overloads" or not adding to my confusion of what it actually does...
If you want to overload the base method, this method must be declared 'Overloads'.
The error message is too generic. Note how it talks about a method even though the warning is about a property. You cannot overload a property.
If I were the King of France, I would have written the error message like:
Property 'Species' hides the 'Species' property inherited from the 'Parent' base class. Use the Shadows keyword to suppress this warning if hiding was intended. Change the name of the property if hiding was not intended.
This warning should almost never be ignored because is almost always identifies a code smell. Using the Shared keyword for Child.Species is very strange and almost certainly not what you intended. Any code that uses your Child object through a reference of type Parent will always get the wrong species name since it will use the base property. The more sane thing to do here is to declare the Parent.Species property Overridable and use the Overrides keyword in Child.Species property declaration, without Shared.
If you shadow a member - the base class will still use it's version of the member when called. For example, if a function in your base class called Species it would still get the value "Should get species from a child."
In this case, overloading will cause functions in the base class to use the child's value.
To make this a bit clearer... the following code's message box says "Original Value", not "New Value"
Public Class Form1
Dim X As New Child
Dim Y = MsgBox(X.ShowMe)
End Class
Public Class Parent
Public Function ShowMe() As String
Return member
End Function
Public Property member As String = "Original value"
End Class
Public Class Child
Inherits Parent
Public Property member As String = "New value"
End Class

How to assign a conditional value prior to get/set in ascx vb.net

In the members region I declare _Name and assign it the value "NameA" but how do I assign this value conditionally?
The idea is that if in some file there's a value set than that should be the default value and not the hard coded one, if non is set the hard coded one should stick.
Is there an official good way of doing this or is or is wrecking the get/set the only way to accomplish this?
#Region "Members"
Private _Name As String = "NameA"
#End Region
#Region "Properties"
Public Property Name() As String
Get
Return _Name
End Get
Set(ByVal value As String)
_Name = value
End Set
End Property
#End Region
Maybe your problem is the way your looking to that.
if you explicitly declare a variable assigning a value, you loose the "dynamic requirement", if you are creating a class with properties, properties are the good way to expose a private member.
if you what to create a instance of this class assigning different values, why you dont pass that info by parameters on the class contructor?