Is there a native LINQ way to return a typed collection in this example? - vb.net

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() _
)

Related

Cannot pass List of subtype to a List of interface type (implemented by subtype) to a function in Visual Basic + Cannot be casted either

I need to use only 1 function to process a list containing interface types so that I can reuse it for each subtype, but I also need to know the concrete type when handling the list-items higher up the function call stack:
LoadAItems calls GetA which calls FilterItems.
FilterItems needs to be generic so that GetB can also call and make use of it.
The problem is that trying to pass the list of subtypes to the general FilterItems method is not allowed:
"Cannot convert type 'List(Of AListItem)' to parameter type 'List(Of IListItem)'"
I tried converting each AListItem object in the list to IListItem and adding it to a new list but the problem is that the FilterItems function is supposed to remove elements from the list. If I remove elements from the new list then it will not affect the old list. I could convert it back but this is a lot of hassle just to be able to use the function.
I cannot just change everything to List(Of IListItem) because then I would need to always cast down the returned value from either FilterItems or GetA / GetB because LoadAItems / LoadBItems needs to know the concrete type.
I can see why casting down is bad, but why can't I cast up to the interface type the concrete types are implementing?
I have already tried:
FilterItems(CType(items, List(Of IListItem))
but this is not allowed:
"Value of type 'List(Of AListItem)' cannot be converted to 'List(Of IListItem)'"
Here is my code example:
Public Class AListItem
Implements IListItem
'Properties here
End Class
Public Class BListItem
Implements IListItem
'Properties here
End Class
Private Sub FilterItems(items As List(Of IListItem))
'Remove items from the list that meet some condition
items.RemoveAll(Function(item) ...)
'Does not matter what the items class type is
End Sub
Public Function GetA() As List(Of AListItem)
Dim items As List(Of AListItem)
items = CallDatabase()
FilterItems(items) ' Does not allow!
Return items
End Function
Public Function GetB() As List(Of BListItem)
Dim items As List(Of BListItem)
items = CallDatabase()
FilterItems(items) ' Does not allow!
Return items
End Function
Public Sub LoadAItems()
Dim items As List(Of AListItem)
items = GetA()
'Do specific AListItem stuff (cannot use interface!)
End Sub
Public Sub LoadBItems()
Dim items As List(Of BListItem)
items = GetB()
'Do specific BListItem stuff (cannot use interface!)
End Sub
If I correctly understand what you're trying to do you should be able to make the function itself generic:
Private Sub FilterItems(Of T As IListItem)(items As List(Of T))
Of T As IListItem adds a constraint that T must be, or inherit from IListItem.
Then you can call it like:
Public Function GetA() As List(Of AListItem)
Dim items As List(Of AListItem)
items = CallDatabase()
FilterItems(Of AListItem)(items)
Return items
End Function
Public Function GetB() As List(Of BListItem)
Dim items As List(Of BListItem)
items = CallDatabase()
FilterItems(Of BListItem)(items)
Return items
End Function
Rather than having a function that takes and manipulates a List(Of IListItem), instead create a function with a signature of:
Private Boolean IsGoodItem(IListItem)
With a hopefully straightforward conversion of your existing code within FilterItems to fit there. Then just change:
Public Function GetA() As List(Of AListItem)
Dim items As List(Of AListItem)
items = CallDatabase()
FilterItems(items) ' Does not allow!
Return items
End Function
To
Public Function GetA() As List(Of AListItem)
Dim items As List(Of AListItem)
items = CallDatabase()
Return items.Where(IsGoodItem).ToList()
End Function
And you still get (a fair bit of) code reuse without stumbling over covariance/contravariance issues. (I think the compiler will be happy with inferring types for the Where or you may have to insert explicit type annotations here)

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.

Property of type dictionary

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.

VB Polymorphism Constructors Default and Properties. Similar Class to Listbox

I've been banging my head against a wall for sometime on this one.
I'm trying to create a class for storing data on People with another class to store their Bank Transactions.
Ideally, this all be hidden away and leave only simple statments, declarations and functions available to the programmer. These will include:
Dim Clients As New ClientList
Clients.Count 'readonly integer
Clients.Add("S")
Clients.Refresh()
Clients(n).Remove()
Clients(n).Transaction.Add()
Clients(n).Transaction(n).Remove()
I know this is possible as these exist in the Listbox Class though can't figure out how it's done.
Any help would be appreciated.
Thanks in advance!
Create a Transaction and a Client class
Public Class Transaction
'TODO: Implement Transaction
End Class
Public Class Client
Public ReadOnly Transactions As List(Of Transaction) = New List(Of Transaction)
Public Sub New(ByVal name As String)
Me.Name = name
End Sub
Public Name As String
End Class
Create a ClientList class
Public Class ClientList
Inherits List(Of Client)
Public Overloads Sub Add(ByVal name As String)
Add(New Client(name))
End Sub
Public Sub Refresh()
' Do what ever you want Refresh to do
End Sub
End Class
You can then use the client list like this
Dim clients As New ClientList
clients.Add("S")
' Or
clients.Add(New Client("T"))
Dim n As Integer = clients.Count
Dim m As Integer = clients(0).Transactions.Count
clients.Refresh()
clients.RemoveAt(5)
clients(n - 1).Transactions.Add(New Transaction())
clients(n - 1).Transactions.RemoveAt(2)
Dim name As String = clients(0).Name
Dim client As Client = clients(0)
Use the generic List(Of T) class, specialized to hold your Client objects. It already provides all of the methods you want without your having to write a single line of code!
So you would first write a Client class that contained all of the properties (data) and methods (actions) relating to a "client":
Public Class Client
Public Property Name As String
Public Property AmountOwned As Decimal
Public Sub Bill()
BillingManager.BillClient(Me)
End Sub
' ... etc.
End Class
Then, you would create the List(Of T) to hold all of your instances of the Client class:
Dim clients As New System.Collections.Generic.List(Of Client)
If, for whatever reason, you needed to specialize the behavior of the Add, Remove, etc. methods provided by the collection class, or add additional methods, you would need to change strategies slightly. Instead of using List(Of T), you would inherit from Collection(Of T) and create a custom collection class like so:
Public Class ClientCollection
Inherits System.Collections.ObjectModel.Collection(Of T)
' ... customize as desired ...
End Class
The WinForms ListBox class doesn't do it exactly like this because it was written before generics were introduced to the framework. But since they're here now, and you should always use them when possible, you can completely ignore how WinForms does things.

Create a dropdown list of valid property values for a custom control

I've created a custom user control that has several properties. One specifies which database I want the control to access. I want to be able to present the user of the control a drop down from which he can select which database the control will interact with.
How do I get the dropdown to work? I can get default values, but have yet to figure out how to get the selectable list.
Any help is apprectiated.
Thanks.
Marshall
You just need to attach your own TypeConverter to your property. You will override the GetStandardValuesSupported and GetStandardValues methods (maybe GetStandardValuesExclusive too) to return the list of databases you want to show.
If you are new to the PropertyGrid and the TypeConverter, here is a document.
It turns out to be simpler than I thought.
I had an enumeration set up for the property, but was having trouble using it for the property type. Said it was unaccessable outside of the class.
Then I had a 'duh' moment and changed the enumeration from Friend to Public, and then I was able to use the enumeration as the property type. As a result the values from the enumeration are listed in a dropdown when I look at the values for that property of the controls.
Thanks to all that answered.
Marshall
I'm a little confused about your problem.
If your user control contains a DropDownList control, just inititalize somewhere in the user control.
The easiest way is in the Codebehind for the usercontrol, just do DropDownList.Items.Add() or whatever the syntax is for adding an item.
Here is the template I use to create a custom datasource for my comboboxes:
Private Class Listing
Private _List As New ArrayList
Public Sub Add(ByVal ItemNumber As Integer, ByVal ItemName As String)
_List.Add(New dataItem(ItemNumber, ItemName))
End Sub
Public ReadOnly Property List() As ArrayList
Get
Return _List
End Get
End Property
End Class
Private Class dataItem
Private _ItemNumber As Integer
Private _ItemName As String
Public Sub New(ByVal intItemNumber As Integer, ByVal strItemName As String)
Me._ItemNumber = intItemNumber
Me._ItemName = strItemName
End Sub
Public ReadOnly Property ItemName() As String
Get
Return _ItemName
End Get
End Property
Public ReadOnly Property ItemNumber() As Integer
Get
Return _ItemNumber
End Get
End Property
Public ReadOnly Property DisplayValue() As String
Get
Return CStr(Me._ItemNumber).Trim & " - " & _ItemName.Trim
End Get
End Property
Public Overrides Function ToString() As String
Return CStr(Me._ItemNumber).Trim & " - " & _ItemName.Trim
End Function
End Class
And this is how I load it:
ListBindSource = New Listing
Me.BindingSource.MoveFirst()
For Each Row As DataRowView In Me.BindingSource.List
Dim strName As String = String.Empty
Dim intPos As Integer = Me.BindingSource.Find("Number", Row("Number"))
If intPos > -1 Then
Me.BindingSource.Position = intPos
strName = Me.BindingSource.Current("Name")
End If
ListBindSource.Add(Row("Number"), strName)
Next
cboNumber.DataSource = ListBindSource.POList
cboNumber.DisplayMember = "DisplayValue"
cboNumber.ValueMember = "Number"
AddHandler cboNumber.SelectedIndexChanged, AddressOf _
cboNumber_SelectedIndexChanged
Hope this helps. One thing to keep in mind is that if cboNumber has a handler already assigned to the SelectedIndexchanged event, you will encounter problems. So don't create a default event.