Evaluate expression - vb.net

So, I have an object with some properties, like this: Dim.d1, Dim.d2,...,Dim.d50
that return strings. For example: Dim.d1="Description A", Dim.d2="Description B",etc.
What I want to do is to attribute these descriptions to the headers of a Gridview and for that I was thinking using indexes, like this pseudocode:
for i=0 until 49
e.Row.Cells[i].Text = Evaluate(Dim.d(i+1))
So, basically, I need a way to change the call to my object properties depending on the index, but I don't know if it is possible. When index i=0, call Dim.d1, when index i=1 call Dim.d2, and so on until 50.
Any ideas?

This is what Arrays or Lists are for!
var dim = new string[50];
dim[0] = "Description A";
dim[1] = "Description B";
..// etc
for(var i=0;i<49;i++)
{
e.Row.Cells[i].Text = dim[i];
}

You can use methods in the System.Reflection namespace to do this. However, the answer is presented in order to answer the question - you should look at using some of the options suggested by other answerers e.g. use a List(Of String) or something similar.
Anyway, let's say you have a class:
Public Class Class1
Public Property d1 As String
Public Property d2 As String
Public Property d3 As String
End Class
And then, let's say you create an instance of that class and set its properties:
Dim obj As New Class1
obj.d1 = "Foo"
obj.d2 = "Bar"
obj.d3 = "Test"
If you then want to have a loop from 1 to 3, and access e.g. d1, d2, d2 etc then this is where you use Reflection:
For i As Integer = 1 To 3
Dim info As System.Reflection.PropertyInfo = obj.GetType().GetProperty("d" & i)
Dim val As String = info.GetValue(obj, Reflection.BindingFlags.GetProperty, Nothing, Nothing, Nothing)
Debug.Print(val.ToString)
Next
Will give you the output:
Foo
Bar
Test

Like Jamiec already posted, use an Array or List.
Where do you description labels come from?
If you have your descriptions in a comma separated string, here is the vb.net code:
dim descriptions as String = "Description A,Description B,Description C"
dim myArray as String() = descriptions.Split(cchar(","))
for i as Integer = 1 To myArray.Length
e.Row.Cells(i-1).Text = myArray(i)
Next

Related

How is performance going to be if I iterate through an object to get all the properties?

For example, let's say I have a simple class and I created an object for said that...
Public Class StackOverflow
Public Property Questions As String
Public Property Answers As String
Public Property Accepted As Integer
Public Property Boohoo As Boolean
End Class
Dim Noobie As New StackOverflow With {
.Questions = "How do I ?",
.Answers = "Like This",
.Accepted = 1,
.Boohoo = True}
Let's say I have 1000 labels, each label contains a StackOverflow with its own content. When I mouse over the label, I want to show each of those properties in a popup. To be able to do this, from my search results of the answers on StackOverflow, it seems I have to use Reflection. And according to the other developers on here, using reflection is slow and I should only use it if I have to.
Is there a better way of iterating through the object to get all the information so I can display it, depending on the label that is mouse over?
EDIT: Adding some more details to my post. I am creating a custom map and I am plotting points onto that map. When I create a point, I inherit the class so it can contain some more information. For example...
Public Class PinPoint
Public Property X as Double
Public Property Y as Double
Public Property ExtraInfo1 as String
Public Property ExtraInfo2 as String
End Class
And when I create a new point for my map, I would do something like :
Dim Pin As New PinPoint With {.X = Xcoord, .Y = Ycoord, .ExtraInfo1 = "Info1", .ExtraInfo2 = "Info2"}
And when I mouse over those points...
Public Sub PinMouseOver()
Dim rowx As Label
Dim coly As Label
'Create a new Row and Col for the title
TableLayoutPanel1.RowStyles.Add(New RowStyle(SizeType.AutoSize))
TableLayoutPanel1.ColumnStyles.Add(New ColumnStyle(SizeType.AutoSize))
TableLayoutPanel1.RowCount += 1
TableLayoutPanel1.ColumnCount += 1
rowx = New Label With {.Text = "Title: "} : coly = New Label With {.Text = Pin.Title}
TableLayoutPanel1.Controls.Add(rowx, 0, TableLayoutPanel1.RowCount - 1)
TableLayoutPanel1.Controls.Add(coly, 1, TableLayoutPanel1.ColumnCount - 1)
'And then do the same for all the other properties.
End Sub
I have something which does almost this
<Runtime.CompilerServices.Extension>
Public Function AllPropertiesString(instance As Object) As String
Try
If instance Is Nothing Then Return ""
Return String.Join(Environment.NewLine,
instance.GetType().
GetProperties().
Select(Function(pi) $"{pi.Name}{vbTab}{pi.GetValue(instance)}"))
Catch
Return ""
End Try
End Function
usage
Dim Noobie As New StackOverflow With {
.Questions = "How do I ?",
.Answers = "Like This",
.Accepted = 1,
.Boohoo = True}
Dim result = Noobie.AllPropertiesString()
Console.WriteLine(result)
output
Questions How do I ?
Answers Like This
Accepted 1
Boohoo True
and you can just format the returned string how you like
Based on your comment, you can return a Dictionary(Of String, Object) and manipulate the names and values how you wish.
<Runtime.CompilerServices.Extension>
Public Function AllPropertiesDictionary(instance As Object) As Dictionary(Of String, Object)
Try
If instance Is Nothing Then Return Nothing
Return instance.GetType().GetProperties().ToDictionary(Function(pi) pi.Name, Function(pi) pi.GetValue(instance))
Catch
Return Nothing
End Try
End Function

Variables refer to the same instance

Within my learning curve I play around with converting List and IEnumerable between each other.
What I am surprised with is that after executing EditMyList procedure MyIEnumerable contains the same data for each DBTable object as MyList. However I have modified MyList only, without assigning it to MyIEnumerable once List has been modified.
Can you explain what happened here and why MyList and MyEInumerable refer to the same instance?
Public Class DBTable
Public Property TableName As String
Public Property NumberOfRows As Integer
End Class
Public Sub EditMyList
Dim MyList As New List(Of DBTable)
MyList.Add(New DBTable With {.TableName = "A", .NumberOfRows = 1})
MyList.Add(New DBTable With {.TableName = "B", .NumberOfRows = 2})
MyList.Add(New DBTable With {.TableName = "C", .NumberOfRows = 3})
Dim MyIEnumerable As IEnumerable(Of DBTable) = MyList
For Each item In MyList
item.NumberOfRows += 10
Next
End Sub
UPDATE: string case where at the end b is not equal to a. String is also reference type, so assigning one variable to other one we shall copy just reference. However at the end there is different result than in the first example (explained by #Sefe)
Dim a As String
Dim b As String
a = "aaa"
b = "bbb"
a = b
' At this point a and b have the same value of "bbb"
a = "xxx"
' At this point I would expect a and b equal to "xxx", however a="xxx" but b="bbb"
A List is a reference type. That means it is created on the heap and your MyList variable contains just a reference (sometimes incorrectly called "pointer") to the list. When you assign MyList to MyEnumerable you don't copy the whole list, you just copy the reference. That means all changes you make to the (the one) list, is reflected by all the references.
If you want a new list you need to create it. You can use the list constructor:
Dim MyIEnumerable As IEnumerable(Of DBTable) = New List(Of DBTable)(MyList)
Since you don't need a list, but an IEnumerable you can also call the list's ToArray method:
Dim MyIEnumerable As IEnumerable(Of DBTable) = MyList.ToArray
You can also use LINQ:
Dim MyIEnumerable As IEnumerable(Of DBTable) = MyList.ToList
As far as the behavior of String is concerned, strings in .net are immutable. That means once created, they can not be changed. String operations (for example concatinations) will always create new strings. In other words: the copy operation you have to do manually for your lists is done automatically for strings. That's why you see similar behavior for strings as for value types.
Also, the assignment operation in your question would also still behave the same if strings were mutable. When you assign a = "xxx", you update the reference of afrom "bbb" to "xxx". That however does not affect b, which still keeps its old reference.
Use ToList() extension method for creating another List
Dim newCollection = MyList.ToList()
But notice that instances of DBTable still will reference to the same items
For creating "full" copy you need create new instances of DBTable for every item in the collection
Dim newCollection = MyList.Select(Function(item)
return new DBTable
{
.TableName = item.TableName,
.NumberOfRows = item.NumberOfRows
}
End Function).ToList()
For Each item in MyList
item.NumberOfrows += 10 ' will not affect on the newCollection items
Next

How to refer to an enum by it's string name?

I want to refer to an enum by the string name of its root. Please note, i wish to refer to the enum, not an enum member.
There are many posts on stackoverflow describing how to refer an enum member by its name (eg
How to retrieve an Enum member given its name), but i didn't find any about how to refer to the enum by the name of it's root.
To further clarify;
Enum MyEnumA : Quiet : Noisy : End enum
Enum MyEnumB : Big : Small : Gigantic : End enum
Sub Foo(strAction as string)
' Depending on value of strAction, i want to create a list of either MyEnumA or MyEnumB members
' I know i can't do the following, it's just to make clear the direction i'm wanting to go -
Dim lstMembers As New List(Of CType(strAction,[Enum]))
'....
end function
Following the good suggestions below, i've tried this;
Dim enumType As Type = System.Type.GetType("ExcelInterface.clsBTAnalyseRslts+" & "strAction")
Dim lstFldIndx As New List(Of enumtype) 'Fails to compile this line as doesn't recognize enumtype as defined
Thank you!
Give this a go, it creates an array so you can change it to a List(Of x...) later:
Sub Foo(ByVal strAction As String)
Dim exAssembly = Reflection.Assembly.GetExecutingAssembly
Dim enumType = exAssembly.GetTypes.First(Function(f) f.Name = strAction)
Dim myEnum = [Enum].GetValues(enumType)
End Sub
I found an interesting post here which gave me some direction.
usage:
Foo("MyEnumA")
I will leave you to do some error handling and checking :D
Here's a code block that converts the enum into a string and then gets the type based on the string. It will show you how to get the type and how you should format the string. The rest should be pretty straight forward.
Dim obj As MyEnumA
Dim t As Type = obj.GetType()
Dim s As String = t.FullName
Dim t2 As Type = System.Type.GetType(s)
Then do this to get the values:
Dim Members() As String
Members = System.Enum.GetNames(t2)
Variable s will look something like this "namespace.class + MyEnumA" so all you need to do is to create this string programmatically and send it to a function.
Ric's answer didn't quite work for me because my Enum wasn't in the same location as my code, so here's how I finally managed it (I use VB, but I'll add C# for reference):
VB
Dim EnumName As String = "MyEnum"
Dim MyAssembly As System.Reflection.Assembly = System.Reflection.Assembly.GetAssembly(GetType(My.Assembly.Location))
Dim MyEnum As System.Type = MyAssembly.GetType("My.Assembly.Location+ClassName+" & EnumName)
Dim EnumNames As String() = MyEnum.GetEnumNames()
For intVal As Integer = 0 To EnumNames.Length - 1
' EnumNames(intVal) = Name of Enum Value
' intVal = Enum Value
Next
C#
string EnumName = "MyEnum";
System.Reflection.Assembly MyAssembly = System.Reflection.Assembly.GetAssembly(typeof(My.Assembly.Location));
System.Type MyEnum = MyAssembly.GetType("My.Assembly.Location+ClassName+" + EnumName);
string[] EnumNames = MyEnum.GetEnumNames();
for (int intVal = 0; intVal <= EnumNames.Length - 1; intVal++) {
// EnumNames(intVal) = Name of Enum Value
// intVal = Enum Value
}
Hopefully it will save someone's valuable time :).

.NET: Quick way of using a List(Of String) as the Values for a new Key/Value collection?

I have a List(Of String) object and need to create a new collection based on the strings' values. The new collection will be of a custom class with two string fields - call them Key and Value (but it's not the built-in KeyValue class, it's a custom class).
All the values of Key will be the same, it's just Value that I want to source from the string list. Example:
Dim slValues = New List(Of String)({"Cod", "Halibut", "Herring"})
Dim myList = New List(Of myClass)( ... amazing initialisation line here? )
Class myClass
Public Key As String ' This will always be "Fish"
Public Value As String ' This will be the fish name.
End Class
(Note I don't actually have access to the class I'm using, so can't just change it to Public Key As String = "Fish" as a default. Key must be set at runtime.)
I can of course just do a loop and do it manually, but I'm wondering if there's a whiz-bang way to achieve this as part of the initialisation line?
How about this
Dim myList = slValues.ConvertAll(Of myClass)( _
Function(s) New YourClass With {.Key = "Fish", .Value = s})
Here's an alternative to Jodrell's answer using Linq's Select function:
Dim myList = slValues.Select(Function(fish) New CustomClass With {.Key = "Fish", .Value=fish}).ToList()
from x in Enumerable.Range(1, slValues.count-1).ToArray
select new KeyValuePair(of string,myClass) with {.Key=slValues(x),.Value=myList(x)}
hope it helps.

Why won't this list of struct allow me to assign values to the field?

Public Structure testStruct
Dim blah as integer
Dim foo as string
Dim bar as double
End Structure
'in another file ....
Public Function blahFooBar() as Boolean
Dim tStrList as List (Of testStruct) = new List (Of testStruct)
For i as integer = 0 To 10
tStrList.Add(new testStruct)
tStrList.Item(i).blah = 1
tStrList.Item(i).foo = "Why won't I work?"
tStrList.Item(i).bar = 100.100
'last 3 lines give me error below
Next
return True
End Function
The error I get is: Expression is a value and therefore cannot be the target of an assignment.
Why?
I second the opinion to use a class rather than a struct.
The reason you are having difficulty is that your struct is a value type. When you access the instance of the value type in the list, you get a copy of the value. You are then attempting to change the value of the copy, which results in the error. If you had used a class, then your code would have worked as written.
try the following in your For loop:
Dim tmp As New testStruct()
tmp.blah = 1
tmp.foo = "Why won't I work?"
tmp.bar = 100.100
tStrList.Add(tmp)
Looking into this I think it has something to do with the way .NET copies the struct when you access it via the List(of t).
More information is available here.
Try creating the object first as
Dim X = New testStruct
and setting the properties on THAT as in
testStruct.blah = "fiddlesticks"
BEFORE adding it to the list.