Suppose I've got a couple of classes, like class1 and class2. These classes can have many properties and they can even change from an execution to another (depending on the version of the software).
The user can define a "formula" in plain text and the software should convert the names of the "variables" (class+property) with their actual values. So, given an instance of class1 and an instance of class2, I should cycle the properties (and variables) and check if the formula contains them. For each one I should then replace the name with the value.
For example, I could have:
Dim myClass1 As Class1 = New Class1()
myClass1.PropertyA = "FOO"
myClass1.PropertyB = 5
Dim myClass2 As Class2 = New Class2()
myClass2.PropertyX = "BAR"
myClass2.PropertyY = 7
And the user could have declared this formula (the formula here is simplified in VB.NET, but actually it can be in SQL or different type. The formula is in plain text, so is a string in code):
Dim i = 0
While (i + myClass1.PropertyB) < myClass2.PropertyY
str = "myClass1.PropertyA" & "myClass2.PropertyX"
i += 1
End While
The result, after the replace, should be:
Dim i = 0
While (i + 5) < 7
str = "FOO" & "BAR"
i += 1
End While
Now, the classes and the variables can vary, so I can't search for specific strings. I think I should use reflection, but I'm not used to it and I prefer to avoid it if a better solution exists. Any advice and/or best practice?
This can be done as a class or as a function
Consider the formula.
x=(aa)+(bb)+(2*a*b)
Public Function formularesult(Byval a1 as double,Byval b1 as double) as double
Dim x as double
x=0
Dim a as double
a=0
Dim b as double
b=0
a =a1
b=b1
x=(a*a)+(b*b)+(2*a*b)
return x
End Function
Call to the function can bs follows
Let values of a and b can be 12 and 20 respectively.
Dim result as double
result =formularesult(12,20)
I hope this can be a help for someone.
Improvements to my suggestions are always welcome.
Related
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
I would like to to copy that values of an array into a Structure.
Example:
' The Array
Dim Columns(2) As String
' The Structure
Private Structure Fields
Public FName As String
Public LName As String
Public Email As String
End Structure
' I would like to map it like so:
Fields.FName = Columns(0)
Fields.LName = Columns(1)
Fields.Email = Columns(2)
Obviously I could write a function if it was so simple, but really there are over 25 columns and it's a pain to write a function that would map it.
Is there some way to do this?
There really is no simple way that will work in all cases. What you are complaining is too much effort is the only way to guarantee that it will work in all cases.
That said, if you can guarantee that the number of elements in the array matches the number of properties/fields in the structure/class and that they are in the same order and of the same types then you could use Reflection in a loop, e.g.
Private Function Map(source As Object()) As SomeType
Dim result As New SomeType
Dim resultType = result.GetType()
Dim fields = resultType.GetFields()
For i = 0 To source.GetUpperBound(0)
fields(i).SetValue(result, source(i))
Next
Return result
End Function
EDIT:
The code I have provided works as is if SomeType is a class but, as I missed the first time around, not for a structure. The reason is that structures are value types and therefore a copy of the original object is being sent to SetValue, so the field value never gets set on that original object. In theory, to prevent a copy being created, you should be able to simply box the value, i.e. wrap it in an Object reference:
Private Function Map(source As Object()) As SomeType
Dim result As Object = New SomeType
Dim resultType = result.GetType()
Dim fields = resultType.GetFields()
For i = 0 To source.GetUpperBound(0)
fields(i).SetValue(result, source(i))
Next
Return DirectCast(result, SomeType)
End Function
As it turns out though, the VB compiler treats that a little differently than the C# compiler treats the equivalent C# code and it still doesn't work. That's because, in VB, the boxed value gets unboxed before being passed to the method, so a copy is still created. In order to make it work in VB, you need to use a ValueType reference instead of Object:
Private Function Map(source As Object()) As SomeType
Dim result As ValueType = New SomeType
Dim resultType = result.GetType()
Dim fields = resultType.GetFields()
For i = 0 To source.GetUpperBound(0)
fields(i).SetValue(result, source(i))
Next
Return DirectCast(result, SomeType)
End Function
Suppose I want to use an If statement, but I won't know until run-time what the actual condition of the If statement will be. Is there a way to do this by passing the condition as the contents of a string? As an example of the kind of thing I'm looking to acheive, consider the following bit of code;
Dim a as Integer = 1
Dim b as Integer = 2
Dim ConditionString As String = "<"
If a ConditionString b Then
...
End If
Mainly what I'm looking for is some way to leave the actual condition undefined until run-time. The reason I want to do this is because I need to have a set of threshold conditions in a database including not just the numeric values themselves, but also comparison operations. I might want to have something that amounts to "> 3.2 And < 5.6". As numbers are pulled in from data, the comparison operations need to be applied to the data depending on various conditions. Also, the database would be changed from time-to-time.
For such cases I love to use NCalc library, it has everything you need - it parses simple expressions (including logical and relational). Here is an example of it in C#:
var expr = new Expression("[X] > 3.2 and [X] < 5.6");
expr.Parameters["X"] = 10.0;
if (expr.Evaluate())
{
// ...
}
and VB.NET:
Dim expr As var = New Expression("[X] > 3.2 and [X] < 5.6")
expr.Parameters("X") = 10
If expr.Evaluate Then
' ...
End If
You can store a map of String to Func(Of Integer, Integer, Boolean) keyed by the strings "<", ">", "==", and so on, and take addresses of the functions that implement those conditions. For example:
Function LessThan(Integer a, Integer b) As Boolean
Return a < b
End Function
Dim Comparisons As New Map(Of String, Func(Of Integer, Integer, Boolean))
Comparisons.Add("<", AddressOf LessThan)
And then you can call it as such:
Dim a as Integer = 1
Dim b as Integer = 2
Dim ConditionString As String = "<"
If Comparisons(ConditionString)(a, b) Then
There are only a few possible conditions so I would just use a Select..Case statement:
Select Case ConditionString
Case "<"
Case ">"
'etc.
Case Else
End Select
Otherwise, you cannot (simply) convert a string "<" to an operator.
You just need a 'code', mapping some kind of value that you can store in a database to the various kinds of "conditions" you wish to test. Instead of a string, I'd suggest using an enum:
Enum ConditionEnum
LessThan
GreaterThan
Equal
SomeOtherVeryComplicatedBinaryFunction
End Enum
And then define a method that evaluates the condition along with the two arguments:
Public Sub EvaluateConditionWithArguments(ConditionEnum condition, Integer a, Integer b) As Boolean
EvaluateConditionWithArguments = False
Select Case condition
Case ConditionEnum.LessThan
If a < b Then
EvaluateConditionWithArguments = True
End If
...
End Select
End Sub
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.
Is it possible to overload the array/dict access operators in VB.net? For example, you can state something like:
Dim mydict As New Hashtable()
mydict.add("Cool guy", "Overloading is dangerous!")
mydict("Cool guy") = "Overloading is cool!"
And that works just fine. But what I would like to do is be able to say:
mydict("Cool guy") = "3"
and then have 3 automagically converted to the Integer 3.
I mean, sure I can have a private member mydict.coolguy and have setCoolguy() and getCoolguy() methods, but I would prefer to be able to write it the former way if at all possible.
Thanks
To clarify - I want to be able to "do stuff" with the value. So for instance, say I have
myclass.fizzlesticks ' String type
myclass.thingone ' Numerical type, say integer
and then I want to be able to write
myclass("thingummy") = "This is crazy"
which fires off a method that looks like this
Private sub insanitea(Byval somarg as Object, Byval rhs as Object)
If somearg = "thingummy" And rhs = "This is crazy" Then
thingone = 4
fizzlesticks = rhs & " and cool too!"
End If
End Sub
This isn't the precise use-case, but I think it does a better job of being able to illustrate what I'm looking for?
No, you can't overload the array access operators in Visual Basic.
Currently the only operators you can overload are:
Unary operators:
+ - Not IsTrue IsFalse CType
Binary operators:
+ - * / \ & Like Mod And Or Xor
^ << >> = <> > < >= <=
Why can't you do the following:
mydict("Cool guy") = 3 //without quotes
Then you can do
dim x = mydict("Cool guy") + 7
//x returns 10
Or, you could do a Int32.tryParse
dim x as integer
if int32.tryParse(mydict("Cool guy"), x) then
return x + 7 //would return 10
end if