Property of type dictionary - vb.net

Is there a way to declare a property of type dictionary of string, string in VB.Net.
I am using this on a usercontrol to add properties via the designer.
I tried the following:
Private v As Dictionary(Of String, String)
Public Property VList As Dictionary(Of String, String)
Get
Return v
End Get
Set(ByVal value As Dictionary(Of String, String))
v = value
End Set
End Property
But when I try this the string collection editor window opens up but the add & remove buttons are disabled. What is the correct way to declare this property?
I want to add the key & value via the designer.

The Dictionary does not have a built in UITypeEditor. There are many reasons why there isn't: there are 2 Types which are generic, it also doesnt have an Item accessor, there is no simple Add method, the key must be unique and there is no built in way to serialize a Dictionary "item".
The right way is to use a Collection class inheriting from Collection<T> so you can control access to the contents (note: this is from System.Collections.ObjectModel not the horrible VB Collection!). The fast way to setup a working interface is to use a List(Of myTypeClass), but this is dangerous in production code because it allows all sorts of actions on the innerlist which you likely do not want.
<Serializable><TypeConverter(GetType(FooConverter))>
Public Class FooBar
<DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)>
Public Property Name As String
<DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)>
Public Property Value As String
' simple ctor REQUIRED for the UITypeEditor
Public Sub New()
Name = ""
Value = ""
End Sub
' ctor for the TypeConverter (NOT included)
Public Sub New(n As String, v As String)
Name = n
Value = v
End Sub
Public Overrides Function ToString
Return Name
End Sub
End Class
' must be instanced
Private myFoo As New List(Of FooBar)
' list is an object so it cant be serialized, but the CONTENTS can be
<DesignerSerializationVisibility(DesignerSerializationVisibility.Content)>
Public Property FooList As List(of FooBar)
Get
If myFoo Is Nothing Then
myFoo = New List(of FooBar)
End If
Return myFoo
End Get
Set
' do nothing
End Set
End Sub
' for designer serialization
Private Function ShouldSerializeFooList As Boolean
Return myFoo.Count > 0 ' or myFoo IsNot Nothing
End Sub
public Sub ResetMyFolist
myFoo = New List(of FooBar)
End Sub
Caveats:
It is almost always better to write a class container for the Foobar items. Usually you would inherit from Collection<T>. List<T> as shown is a container and a collection, so the contents can be cleared, reset, modified etc when exposed as shown. They are fast and easy to implement though and the basic concept is the same.
If a Dictionary is really what you want, you can write your own UITypeEditor (not UIDesigner, this is not a control) but this would probably require a great deal of work on many levels. The reason there are not gobs of them flying around is that most people make do with one of the standard collections and simply enforce unique names in other ways. (Adding "Properties" to a usercontrol, suggests that really the key or name ought to be fixed and known to the app ahead of time so it knows what it is and what to do with it(?)).
Often VS can perform designer serialization on its own with simple properties like those in FooBar. However, since they are items in a collection, you will likely need to also write a TypeConverter which can return an InstanceDescriptor, to help VS instance them. But that is a different question.

Related

Allow an object's List(Of T) property to be amended but not replaced

If I create an object like this...
Public Class SomeClass
Public Property SomeList As New List(Of Int32)
End Class
...I can alter the list using the normal methods:
Dim s As New SomeClass()
s.SomeList.Add(123)
But, is it possible to allow the above access to the list, but prevent the whole list being replaced by another list instance? For example, prevent this:
Dim s As New SomeClass()
Dim lst As New List(Of Int32)
lst.Add(1)
s.SomeList = lst ' <-- prevent a replacement list being passed
I notice that when using the Net.MailMessage class, there is a Property called To where this seems to have been applied. I can add an email address to the list...
Dim mm as New MailMessage
mm.To.Add(New MailAddress("me#company.com"))
...but I cannot replace the MailAddressCollection:
Dim mm As MailMessage
Dim mc As MailAddressCollection
mm.To = mc ' Error: Property 'To' is 'ReadOnly'
How is this achieved please? I tried to decompile the source of MailMessage but there is so much code I'm struggling to see how it is done.
There are two ways..
Private _SomeList As New List(Of Int32)
Public ReadOnly Property SomeList As IList(Of Int32)
Get
Return _SomeList
End Get
End Property
..as Konrad pointed out in the comments. Having the property return the IList Interface instead of List is a style thing. If you run code analysis, it will suggest returning the IList instead of List.
That will prevent the caller from replacing the list with a whole new list or setting it to Nothing, but there's nothing to stop them from doing something like...
someInstance.SomeList.Clear()
someInstance.SomeList.AddRange(newListOfStuff)
If you really want to restrict what the caller can do with it, you can leave the list private and just implement methods to let the caller do what you want to allow them to do...
Private _SomeList As New List(Of Int32)
Public Sub AddToSomeList(val As Int32)
_SomeList.Add(val)
End Sub
Now the caller can add to the list but not remove or clear the list.

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

is this workaround/architecture/approach correct using classes (complex properties)?

I'm kind of confused using OOP, I mean, I already know (or I guess) what's a Class, and Object, a Method, a property and things like that, but I have this doubt:
Let's supppose I have a class "Laundry" this class has (just for this example) 3 properties:
laundryID
Name
Washers
Id and Name are "primitive" types or simple types (integer and string), but Washers, represent a list of object of type "Washer", that has 3 properties:
washerID
laundryID
capacity
brand
All of them (for this example) simple/primitives types (integer,string,string).
So this my dilemma, it is correct to have a constructor like this (VB .net)
public sub new(ByVal laundryID as integer)
'' Here I'll query for the data of the Laundry on DB
'' After that I will query for every washer that belongs to this laundry
'' Then I will create a washer object for every row and add it to the
'' property "Washers" of the "Laundry"
end Sub
And inside of that constructor query for all the washers that belongs to the laundry with id=laundryID parameter, and create objects of type "Washer" and add them to the list of Laundry?
I'm not sure If I explain myself, any comment I'll be really grateful, in less words, what's the correct approach (or good practices) to work with class/objects that has properties which are complex (other class/objects).
I usually work with ADO.net probably ADO/LINQ/Entity solve this thins in an transparent way, but if they do it I don't really know it.
Note: I'm familiar with VB.net, C#, PHP, so if you prefer explain/help me using examples with its syntax, it's ok.
You have many options in the OOP world, I've included two here:
Load during the Laundry constructor
Load when the Laundry.Washers are first referenced (lazy load)
The main difference here is that if you're loading 1000 Laundry objects (say to display them in a list) you won't have the 1000 individual queries firing off to load their Washers. If someone selects the Laundry and drill down, the Washers are loaded when you need the data.
Public Class Laundry
Private _laundryId As Integer
Private _name As String
Private _washers As List(Of Washer)
Public Sub New(ByVal LaundryId As Integer)
_laundryId = LaundryId
' option one - load them when the Laundry class loads
LoadWashers()
End Sub
Public ReadOnly Property Washers As List(Of Washer)
Get
If _washers Is Nothing Then
' option two - load them the first time the washers properties are referenced
LoadWashers()
End If
Return _washers
End Get
End Property
Private Sub LoadWashers()
_washers = New List(Of Washer)
' load the washers here
'...
'...
End Sub
End Class
Your constructor is fine. You are basically saying that you can only make a Laundry Class if you have an ID number, in which case you should probably throw an exception if laundryID is not a valid id number.
I usually expose something like the Washers list as an IEnumerable(Of Washer), so that the consumers of my class can't control the list themselves.
This would be a simple example:
Public Class Laundry
Private _LaundryID As Integer
Private _LaundryName As String
Private _Washers As List(Of Washers)
Public Sub New(ByVal laundryID As Integer)
_LaundryID = laundryID
_Washers = New List(Of Washers)
'// populate washers
'// throw exception if something went wrong
End Sub
'// Properties
Public ReadOnly Property Washers As IEnumerable(Of Washer)
Get
Return _Washers
End Get
End Property
End Class
How you create a new Laundry object is up to you. It could be just an empty constructor, or a Shared function that returns a new Laundry object for you with a pre-populated ID number from the database (or wherever).

Is there a native LINQ way to return a typed collection in this example?

Is this the most straight forward way to return a typed collection?
Sorry for the repeated question. Now I am looking for a better way...
Here's the key line of code which returns a implicit type of IEnumerable that used to manually loop through to manually recreated a TYPED collection. Is there any native LINQ way to return a typed sorted collection without this recreating of the collection?
Dim Sorted As ErrorProviderMessageCollection = New ErrorProviderMessageCollection(From item In MyCollection Order By item.Control.TabIndex)
Public Class ErrorProviderMessageCollection
Inherits System.Collections.ObjectModel.Collection(Of ErrorProviderMessage)
Public Sub New()
End Sub
Public Sub New(ByVal source As IEnumerable(Of ErrorProviderMessage))
Dim orderedCollection = New ErrorProviderMessageCollection()
For Each Item As ErrorProviderMessage In source
Me.Add(Item)
Next
End Sub
End Class
Public Class ErrorProviderMessage
Public Sub New(ByVal message As String, ByVal control As Control)
_Message = message
_Control = control
End Sub
Public ReadOnly Property Message() As String
Public ReadOnly Property Control() As Control
End Class
The key point to remember about LINQ is that it's based on deferred execution.
If you do
Dim y = col.Where(Function(i) i.SomeProp = True)
You AREN'T actually creating a new collection. LINQ is creating an enumerator that executes on each item in col on demand.
So, the short answer is, no, LINQ yields items in an enumerable, it doesn't return new collections (with the exception of methods like ToList or ToArray or ToDictionary which force enumeration).
If you want a new typed collection, you need to force enumeration:
Dim col2 = New ErrorProviderMessageCollection(col.Where(Function(i) i.SomeProp = True))
No, there is no LINQ way of doing that, as the Collection<T> class is not one of the collection types that it uses.
You can turn the IEnumerable<T> into a List<T>, which implements IList<T>, and there is a constructor for Collection<T> that takes an IList<T>:
Dim Sorted As Collection<ErrorProviderMessage> = _
New Collection<ErrorProviderMessage>( _
(From item In MyCollection Order By item.Control.TabIndex).ToList() _
)

.net dynamic loading

I've seen some other responses about this and they talk about interfaces but I'm pretty sure you can do this with classes and base classes but I can't this to work.
Public Class Behavior
Private _name As String
Public ReadOnly Property Name As String
Get
Return _name
End Get
End Property
Public Property EditorUpdate As Boolean
Public Sub New(ByVal name As String)
_name = name
EditorUpdate = False
End Sub
Public Overridable Sub Update()
End Sub
' runs right away in editor mode. also runs when in stand alone game mode right away
Public Overridable Sub Start()
End Sub
' runs after game mode is done and right before back in editor mode
Public Overridable Sub Finish()
End Sub
' runs right when put into game mode
Public Overridable Sub Initialize()
End Sub
' runs when the game is complete in stand alone mode to clean up
Public Overridable Sub Destroy()
End Sub
End Class
Public Class CharacterController
Inherits Behavior.Behavior
Public Sub New()
MyBase.New("Character Controller")
End Sub
Public Overrides Sub Update()
' TODO: call UpdateController()
' THINK: how can UpdateController() get the controller entity it's attached to?
' Behaviors need a way to get the entity they are attached to. Have that set when it's assigned in the ctor?
End Sub
End Class
Dim plugins() As String
Dim asm As Assembly
plugins = Directory.GetFileSystemEntries(Path.Combine(Application.StartupPath, "Plugins"), "*.dll")
For i As Integer = 0 To plugins.Length - 1
asm = Assembly.LoadFrom(plugins(i))
For Each t As Type In asm.GetTypes
If t.IsPublic Then
If t.BaseType.Name = "Behavior" Then
behaviorTypes.Add(t.Name, t)
Dim b As Behavior.Behavior
b = CType(Activator.CreateInstance(t), Behavior.Behavior)
'Dim o As Object = Activator.CreateInstance(t)
End If
End If
Next
Next
When it tries to convert whatever Activator.CreateInstance(t) returns to the base class of type Behavior I'm getting invalid cast exception. That type should be of CharacterController which is defined as a child of Behavior so why wouldn't it let me cast that? I've done something like this before but I can't find my code. What am I missing?
This may not be an answer to your question (it also might resolve your exception -- who knows), but it is something that needs to be pointed out. These lines:
If t.IsPublic Then
If t.BaseType.Name = "Behavior" Then
Should really be changed to one conditional like this one:
If t.IsPublic AndAlso (Not t.IsAbstract) AndAlso _
GetType(Behavior.Behavior).IsAssignableFrom(t) Then
Otherwise, if somebody defines a random type called "Behavior" in their own assembly and derives it from another type, your code will think it is a plugin. Additionally, if someone derives your Behavior type and then derives that type (two levels of inheritance) this code will incorrectly skip over that type. Using the IsAssignableFrom method is a quick and easy way to ensure that one type does actually derive from the specific type you want (instead of any type that shares the same name), even if there is another type in between your types in the inheritance tree. The additional check against t.IsAbstract will also ensure that you don't try to instantiate an abstract subtype of your base plugin type.
This works for me:
Dim ctor As Reflection.ConstructorInfo = _
t.GetConstructor(New System.Type() {})
Dim o As Object = ctor.Invoke(New Object() {})
Dim plugin As Plugin = TryCast(o, Plugin)
(If I find t, I invoke the parameterless constructor.)
[I just realized this is probably what Activator.CreateInstance does, so I replaced my code with yours and it worked your way -- so this probably won't help you]