Sort a list by a function results - vb.net

I want to sort a list by the results of functions that are contained in list items. How would go about doing that?
Here is an example. Let's say I have a following object:
Public Class MyListObject
Public MyText1 As String
Public MyText2 As String
Public Function AddSuffix(ByVal MySuffix As String) As String
Return Mytext1 & MySuffix
End Function
End Class
(massively oversimplified for the sake of example)
After that I have a list of these objects:
Dim ResultList As New List(Of MyListObject)
Now, for example, to sort the list by the field MyText1 value, I used this:
ResultList = ResultList.OrderBy(Function(x) GetType(MyListObject).GetField("MyText1").GetValue(x)).ToList
(used reflection, so I can pass the field name as a string)
How can I sort this list by the function AddSuffix result and simultaneously pass the parameter MySuffix?
I imagine it would look something like this, but obviously this doesn't work
ResultList = ResultList.OrderBy(Function(x) GetType(MyListObject).GetField("AddSuffix(""_myCustomSuffix"")").GetValue(x)).ToList
PS. I realize, that in this example the function is pretty much meaningless. The actual code is much more complex and that function does return different sortable data for each list item.

I am guessing you have a pretty good reason to use reflection, but I would recommend avoiding it even if you have too many fields and methods.
ResultList = ResultList.OrderBy(Function(o) o.AddSuffix("_myCustomSuffix")).ToList
The VB way of calling a method by name is something like this (also not tested)
ResultList = ResultList.OrderBy(Function(o) CallByName(o,
"AddSuffix", CallType.Method, "_myCustomSuffix")).ToList

Related

How do I write a VB class which allows indexing into/calling an instance?

I'm honestly not sure what this is called, and if I did I'm sure I could google it in about 5 seconds.
I want to be able to write a class that I can sort of "index into" the way you do with a collection, eg:
Public Class FooClass
Public Function magicKeyword(param as String) as String
If param = "foo" Then
Return "bar"
Else
Return "baz"
End If
End Function
...
End Class
And then use it like this:
Dim myObj as New FooClass
Dim output as String = myObj("foo") '<-- this is what I want to know how to do
' output = "bar"
What is this called, and what syntax would I use for the function?
Out of curiosity, can this also be done as a Shared function so the class itself can do it? e.g.:
Dim output as String = FooClass("foo")
What you are describing is implementing a custom collection, and Microsoft provides a pretty good guide on how to accomplish this.
It should be said, however, that the collection classes already provided in .NET should be able to handle most use cases. A truly custom collection is overkill in many instances.

Pass an argument to a generic type New constructor in VB.Net Generics

I'm trying to be able to pass a Type parameter to a function called ConvertList, and have that function create some instances of the specified type. So, if I passed in type Foo, the function would create new objects of type Foo and put the created objects into a custom List object (SLMR_OBjList).
The function is in a generic class that is defined:
Public Class BOIS_Collection_Base(Of T)
The function would accept types other than what is passed in the class definition. So, if we create an instance of BOIS_Collection_Base(Of MyTypeA) we may call the function ConvertList(Of MyTypeB).
I want the private variable _convertedList to be of a different type than the class. Is this possible? I can only seem to define it with (Of T).
Here is what I have so far:
Public Class BOIS_Collection_Base(Of T)
Private _convertedList As SLMR_ObjList(Of T) ' I can only seem to define this as (Of T), but want to make sure I can pass in a Type other than the Base(Of T)
Public Function ConvertedObjList(Of myT)() As SLMR_ObjList(Of T) ' Should this be (Of T) or (Of myT) since I want it to use whatever Type is passed in
For Each tempVar In Me.ObjList
Dim newitem As myT = Activator.CreateInstance(GetType(myT), tempVar)
' Next line won't compile, says on newitem 'Value of type 'myT' cannot be converted to 'T'
_convertedList.Add(newitem)
Next
_convertedList.Sort_Direction = Me.Sort_Direction
_convertedList.Sort_Expression_List = Me.Sort_Expression_List
Return _convertedList
End Function
Here is what I would like to be able to do:
Dim mainCollInstance As New BOIS_Collection_Base(Of MyTypeA)
....
'Code that populates the BOIS_Collection_Base.ObjList property with an SLMR_ObjList(Of MyTypeA)
....
' Now I want to take that ObjList, and cast all the items in it to MyTypeB
Dim newListObj As SLMR_ObjList(Of MyTypeB) = mainCollInstance.ConvertList(Of MyTypeB)
Is this possible? Am I going about it wrong?
In response to Plutonix:
If I define _convertedList inside the method, like this:
Public Function ConvertedObjList(Of myT)() As SLMR_ObjList(Of myT)
Dim _convertedList = New SLMR_ObjList(Of myT)
my errors go away, and the method does what I want, but _convertedList is no longer persistant in the object.
If you want to persist the list, then you can't really allow the consuming code to pass a different type for the list each time. That doesn't really make much sense, unless each time it's called, you only want the function to return the portion of the persisted list which contains objects of the given type. If that's the case, then you just need to declare _convertedList As SLMR_ObjList(Of Object) and then filter it and convert it to the correct type as necessary.
If, however, as I suspect is the case, the consumer will always be requesting that it be converted to the same type each time the function is called, then that output type is not really a property of the function call. Rather, it's a property of the whole class. In that case, you should make your class take two generic type arguments, like this:
Public Class BOIS_Collection_Base(Of T, TOut)
Private _convertedList As SLMR_ObjList(Of TOut)
Public Function ConvertedObjList() As SLMR_ObjList(Of TOut)
For Each tempVar As T In Me.ObjList
Dim newitem As TOut = DirectCast(Activator.CreateInstance(GetType(TOut), tempVar), TOut)
' Next line won't compile, says on newitem 'Value of type 'myT' cannot be converted to 'T'
_convertedList.Add(newitem)
Next
_convertedList.Sort_Direction = Me.Sort_Direction
_convertedList.Sort_Expression_List = Me.Sort_Expression_List
Return _convertedList
End Function
End Class
Based on the previous related question and an assumption that MyTypeA and MyTypeB inherit from the same class (never got an answer), you may not need Generics for this. At any rate, this should help with the ctor part of the question. I do not as yet see where Generics fit in since inheritance may do what you want already:
Class MustInherit BiosItem
Public Property Name As String
Public Property TypeCode As String
...
MustOverride Function Foo(args...) As Type
Overridable Property FooBar As String
' etc - the more stuff in the base class the better
End Class
Class TypeA
Inherits ABClass
Public Sub New
MyBase.New ' stuff common to all child types
TypeCode = "A" ' EZ type ID rather than GetType
...
End Sub
End Class
Class TypeB would be the same, but initialize TypeCode to "B". The same for C-Z. These allow you to poll the object rather than needing GetType: If thisObj.TypeCode = "A" Then.... Now, the collection class:
Public Class BIOSItems
Inherits Collection(Of BiosItem)
' inheriting from Collection<T> provides Add, Count, IndexOf for us
' most important the Items collection
'
End Class
Typing the collection as BiosItem will allow TypeA or TypeJ or TypeQ in it. As is, your collection will hold one Type only as it should be. This works because an item which is GetType(TypeA) is also GetType(BiosItem). See also note at the end.
Converting one item to another would seem to be something that would largely be handled by the NEW item being created or converted to. Since they are likely to be very similar then it can be handled by a constructor overload (if they are not similar, well we are well down the wrong road):
' a ctor overload to create the new thing based on the old things props
Public Sub New(oldThing As BiosItem)
MyClass.New ' start with basics like TypeCode, MyBase.New
With BiosItem ' coversion
myFoo = .Foo
myBar = .Bar ' copy common prop vals to self
...
Select Case .TypeCode
Case "B"
myProp1 = .Prop33 ' conversions
myProp3 = .Prop16 + 3.14
...
End Select
' then initialize stuff unique to this type maybe
' based on other props
If .PropX = "FooBar" Then myPropZ = "Ziggy"
End With
End Sub
Code to create, convert, store:
Dim varOldBItem As TypeB = myBiosCol(ndx) ' get old item
Dim varAItem As New TypeA(varOldBItem) ' call the ctor above
myBiosCol.Add(varAItem) ' add new item
myBiosCol.Remove(varoldBItem) ' delete the old if need be
If BOIS_Collection_Base is always supposed to contain MyTypeA, then type it that way (inheriting from Collection<T> still seems in order). If also MyTypeB objects are never added to the collection directly, but converted to MyTypeA first (Edit makes that less clear), then most of the above still applies, except for the inheritance. A ctor overload on MyTypeA could still take an old B object and create itself based on it. I'd be less inclined to do it via the ctor if they do not inherit from the same base class, but it could be done.

Reference to object properties

I have a list of objects of type say Person, and I want to export Person records to an excel-sheet (I am using a proprietary excel component for VB.NET). Using a form with checkboxes the user can specify which Person properties should be exported.
Instead of having an enormous if-then-else tree where I check to see if each checkbox (corresponding to a property) has been checked, I have a data structure where for each property in Person I keep a boolean (checked/unchecked) and the name of the property as a string. I then use two for-loops like this:
For Each p As Person In Persons
...
For Each item As ExportColumnData In ExportColumnTable
...
If item.Checked Then
...
Dim o As Object = CallByName(p, item.PropertyName, CallType.Get, Nothing)
SaveValueToExcelSheet(o)
...
End If
...
Next
...
Next
However, this is not type-safe since I am using CallByName supplying PropertyName as a string. Is there a more elegant and type-safe way I can achieve the same thing? I need some way (other than a string) to reference the properties of these Person objects.
The CallByName function uses reflection to find and execute the property getter by string name, so you are right that it is unsafe in the sense that there will be no compile-time checking done to ensure that the properties by those names actually do exist in the Person type.
Unfortunately, short of a big If/Else block, or something similar, there is no "safe" way to do this in a way which will allow for compile-time type checking. If you want it to check that at compile-time, you need to call the property by name directly in code, and if you are doing that, it will have to be in a big conditional block of some sort.
There are things you could do to minimize or shift the location of the ugliness. For instance, you could create an enumeration of all the Person properties and add a method to the Person class which returns the property value given the enumeration item using a big Select Case block. That would make the logic reusable but not really any less ugly. Not only that, but doing it that way kind of puts the type-checking responsibility on your code, not the compiler.
Alternatively, you could, for instance, set the tag of each CheckBox control to a delegate which takes a Person object and returns the correct property value for that option from the given Person object. Then, in the loop, you could just call the delegate in the tag to retrieve the value. For instance, if you had a delegate like this:
Private Delegate Function GetPersonProperty(x As Person) As Object
Then you could set the Tag of the CheckBox controls like this:
chkFullName.Tag = New GetPersonProperty(Function(x As Person) x.FullName)
chkAge.Tag = New GetPersonProperty(Function(x As Person) x.Age)
Then, in your loop, you could invoke the delegate in the Tag to get the value, like this:
Dim myDelegate As GetPersonProperty = CType(item.Tag, GetPersonProperty)
Dim value As Object = myDelegate.Invoke(p)
But that's rather overly-complicated for such a simple task.
In the end, if the compile-time type checking is really important, I'd just bite the bullet and make the big conditional block. If it's not really that important, I'd just stick with the reflection and put some decent exception handling in the code.
You say you already have a class where you store the information about the properties of your Person class. You can use this to store the PropertyInfos as well.
Here's an example:
Class Person
Public Property Name As String
Public Property Age As Integer
End Class
Class ExportProperty
Public Property [Property] As PropertyInfo
Public Property Export As Boolean
End Class
Sub Main()
'' Create a List(Of ExportProperty) from all public properties of Person
Dim properties = GetType(Person).GetProperties() _
.Select(Function(p) New ExportProperty With { .[Property] = p}) _
.ToList()
'' Say we want to export only the Age field
properties.Single(Function(p) p. [Property].Name = "Age").Export = True
'' Create a person instance to export
Dim pers = New Person With { .Name = "FooBar", .Age = 67 }
'' Only export the properties with Export = True
For Each prop in properties.Where(Function(p) p.Export)
'' Use the PropertyInfo.GetValue-method to get the value of the property
''
Console.WriteLine(prop.[Property].GetValue(pers, Nothing))
Next
End Sub
Your solution is perfectly fine, as long as the contents in ExportColumnData are correct. If these are computed dynamically at runtime, you're fine.
Otherwise, or alternatively, you can do the following: use Type.GetProperties to get a list of PropertyInfo objects. You can then use these instead of a mere String to extract property values in your loop:
Dim o As Object = item.PropertyInfo.GetValue(p, Nothing)

VB.NET (or C#) creating a Dictionary from an existing List without looping

I don't know if this is doable, maybe with Linq, but I have a List(Of MyType):
Public Class MyType
Property key As Char
Property description As String
End Class
And I want to create a Dictionary(Of Char, MyType) using the key field as the dictionary keys and the values in the List as the dictionary values, with something like:
New Dictionary(Of Char, MyType)(??)
Even if this is doable, internally it will loop through all the List items, I guess?
This purpose is fulfilled by the ToDictionary extension method:
Dim result = myList.ToDictionary(Function (x) x.key, Function (x) x.description)
Even if this is doable, internally it will loop through all the List items, I guess?
Of course. In fact, an implementation without looping is thinkable but would result in a very inefficient look-up for the dictionary (such an implementation would create a view on the existing list, instead of a copy). This is only a viable strategy for very small dictionaries (< 10 items, say).
In C# there is the ToDictionary<TKey, TSource>, but yes it will loop :-)
You would call it with something like: myCollection.ToDictionary(p => p.key). In VB.NET I think the syntax is myCollection.ToDictionary(Function(p) p.key)
Yes, it will loop through all the list items. Another thing you can do is create a KeyedCollection for your list:
public class MyTypeCollection : KeyedCollection<char, MyType>
{
protected override char GetKeyForItem(MyType item)
{
return item.key;
}
}
but that won't help you adding the items.

Weird VB.NET array-property situation

I have this weird situation.
I have these two classes:
Public Class Entry
End Class
Public Class Core
End Class
One of the properties of the Core class will be an array of Entry objects. How can I declare it?
Now, the only way to change (add/remove) this array from outside should be using two functions - AddEntry(Ent As Entry) and RemoveEntry(ID As String). Note that here, whoever is calling the AddEntry function should only be bothered with creating an Entry object and passing it. It will be added to the existing array.
But, the Entry array should be accessible like this from outside, for looping through and printing or whatever like this:
' core1 is a valid Core object
For Each Ent As Entry In core1.Entries
MsgBox(Ent.SomeProperty)
Next Ent
Is it possible to expose the Array as a property but restrict modification through functions alone? I know that the logic inside the Add and Remove functions can be inside the setter or getter, but the person wanting to add should pass only a single Entry object.
It is like saying You have readonly access to the array, but for modifying it, just create an object and send it or the ID to remove it. Don't bother about the entire array.
I hope I am making sense.
Why do you want to expose it as an array ?
What I would do, is use a List internally to store the entries. (That List would be private)
Create the necessary public methods (AddEntry / RemoveEntry / ... ), which manipulate the private list.
Then, create a public property which exposes the List, but in a ReadOnly fashion. That is, that property should return an ReadOnlyCollection instance.
Like this:
(I know it is in C#, but that 's my 'main language' - a bit too lazy to convert it to VB.NET)
public class Core
{
private List<Entry> _entries = new List<Entry>();
public void AddEntry( Entry entry )
{
_entries.Add (entry);
}
public ReadOnlyCollection<Entry> Entries
{
get { return _entries.AsReadOnly(); }
}
}
EDIT: VB.Net version provided by MarkJ
Imports System.Collections.ObjectModel
Public Class Core
Private _entries As New List(Of Entry)
Public Sub AddEntry( new As Entry )
_entries.Add (new)
End Sub
Public ReadOnly Property Entries() As ReadOnlyCollection(Of Entry)
Get
Return _entries.AsReadOnly
End Get
End Property
End Class
Create a private field for the array and then create your accessing methods to work with the array internally. In order to expose this array to callers so that they can enumerate it you should expose a property of type IEnumerable(Of T).
This approach is not foolproof, however as a clever caller could simply cast the IEnumerable(Of T) back to an array and modify it so it may be necessary to create a copy of the array and return that as the IEnumerable(Of T). All this has obvious performance penalties as I am sure you already see. This is just one of many issues that can arise when arrays are used as underlying data structures.
You can keep the List private and instead return an IEnumerable. Code generated via Reflector - I hope it's readable:
Public Class Core
Public Sub AddEntry(ByVal Ent As Entry)
Me.entries.Add(Ent)
End Sub
Public Sub RemoveEntry(ByVal ID As String)
Dim pred As Predicate(Of Entry) = Function (ByVal entry As Entry)
Return (entry.Id = ID)
End Function
Me.entries.RemoveAll(pred)
End Sub
Public ReadOnly Property Entries As IEnumerable(Of Entry)
Get
Return Me.entries
End Get
End Property
Private entries As List(Of Entry) = New List(Of Entry)
End Class
Note: I'd recommend using a List<Entry> instead of an array if you'll be adding and removing objects - or perhaps even a Dictionary<string, Entry> given the way you are using it.