cast list to another type and change variable of each element of it - vb.net

I have the next class: SearchResultWithDetails.
It inherits from SearchResult that implements ISearchResult.
In addition, I have a list of ISearchResult that is called result.
I want to cast the result into SearchResultWithDetails and set ShortDescription to be the same value as DetailsText.
ShortDescription and DetailsText are variables of the result.
I tried something like:
results.Cast(Of SearchResultWithDetails).ForEach(Function(item)
item.ShortDescription = item.DetailsText)
But it doesn't change the variable of ShortDescription to be the same as DetailsText.
Any help appreciated!

With option strict On you'll see that the ToList method is not an extension method, it's actually part of the List<T> class. This method accepts an Action<T> aka. sub, not a Func<T> aka. function. Apply all these fixes and the code should look like this:
Single-line:
results.OfType(Of SearchResultWithDetails).ToList().ForEach(Sub(item) item.ShortDescription = item.DetailsText)
Multiline:
results.OfType(Of SearchResultWithDetails).ToList().ForEach(
Sub(item)
item.ShortDescription = item.DetailsText
End Sub)

Related

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.

vb.net equivalent of vb6 function attributes

I have the follow class in vb6:
Public Function NewEnum()
Attribute NewEnum.VB_UserMemId = -4
Attribute NewEnum.VB_MemberFlags = "40"
NewEnum = mcolFields.[_NewEnum]
End Function
What would the equivalent attributes be in vb.net? I know that you have to put attributes in <>and I also found this SO post, however it didn't solve my problem.
GetEnumerator() is the exact equivalent. It gets exposed as NewEnum in <ComVisible(True)> code. Simply implement the System.Collections.IEnumerable interface, the non-generic one.
Some info about this is here: https://christopherjmcclellan.wordpress.com/2015/04/21/vb-attributes-what-are-they-and-why-should-we-use-them/
There is one more special value for VB_UserMemId and that value is -4.
Negative 4 always indicates that the function being marked should
return a [_NewEnum] enumerator.
I would say that in this case you can ignore them. So your equivalent should be something like this:
Public Function NewEnum() As mcolFields
Return New mcolFields
End Function

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.

How to add a "sub property" to a class property

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.

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)