I have an html helper class for my webforms projects. so far it can return strings to create labels and readonly fields.
Public Shared Function DisplayFor(value As String, Optional attributes As String = "") As String
Return [String].Format("<span class='uneditable-input {0}'>{1}</span>", GetSStyle(attributes), value)
End Function
Now I want to create some overloads that can accept passing the entity property so it can internally check the datatype (from attributes) and display the content formatted, for example. Just as MVC does.
The only problem it's that I don't know how to pass a class property as a function parameter.
You can pass a property as an Expression(Of Func(Of MyModel, String)) and by that receive an expression in the method that you can analyze and evaluate:
Public Shared Function DisplayFor(Of TModel, TValue)(model As TModel, expr As Expression(Of Func(Of TModel, TValue))) As String
' Retrieve the value dynamically
Dim compExpr = expr.Compile()
Dim value = compExpr.DynamicInvoke(model)
Dim retVal As String
If value Is Nothing Then
retVal = String.Empty
Else
retVal = value.ToString()
End If
' Analyze expression body
Dim memberAccExpr = DirectCast(expr.Body,
System.Linq.Expressions.MemberAccessExpression)
Dim attr = memberAccExpr.Member.GetCustomAttributes(typeof(MyDisplayAttribute), false).Cast(Of MyDisplayAttribute)().FirstOrDefault();
Return retVal
End Function
Call the method like this:
DisplayFor(myModelVar, Function(m) m.MyProperty)
I hope this sample gives you a rough outline on how to handle this. Please note that especially the analysis of the expression body is simplified. In real world code there would be various checks to make sure that the expression matches your expectations.
Related
I have a function which deserializes some custom serialization sent by an API.
I want to build a generic function so that the deserialized object is not of type Object but of the correct type.
The strings which contain the serialized object can be deserialized into one of the following types:
A String,
an IList(Of String),
an IDictionnary(Of String),
one of many SomeNameContainer classes, all derived from a
BaseContainer class,
an IList(Of SomeNameContainer), or
an IDictionnary(Of SomeNameContainer).
I would like to have a single Function Deserialize(Of T)(MyString as String) as T.
Inside this function, I tried to run some Select Case T: GetType(String):Etc tests in order to separate the different actions to run on MyString, depending on the expected object to create from the deserialization.
For example, deserializing into a SomeNameContainer is normally done via another generic function: Dim Deserialized as SomeNameContainer = GetFromContainer(SomeNameContainer)(MyString)
However, I get quickly limited, mainly because:
I cannot return a String type, because it is unable to cast it
into T.
String is a value type, whilst SomeNameContainer are classes. So it is not possible to add an (Of T As {New}) constraint. Which means I am unable to do something like Dim NameContainer as New T: If TypeOf NameContainer Is BaseContainer in order to apply the same operation to all the classes derived from BaseContainer.
One track I have found is to use CTypeDynamic(Of T)(obj as object), which casts at run-time. That might fix problem 1, but problem 2 is still on.
Function Deserialize(Of T)(MyString as String) as T
Select Case GetType(T)
Case GetType(String)
Return SomeFunction(String) '<- Only run-time casting allowed: Return CTypeDynamic(Of String)(SomeFunction(String))
Case GetType(IList(Of String)
Return SomeOtherFunction(String)
Case GetType(...)
'...
Case Else
Dim MyContainer as New T '<- Not Allowed to use New
if TypeOf MyContainer Is T then
Return GetFromContainer(Of T)(String)
else
'...
End If
End Select
End Function
I could decide to split each Type into a separate function. I would like to avoid so that I do not end up with 6 functions. That is because I also need to run some other operations on the string before it is deserialized. For the story, the strings come under various encoding/encryption formats. So if I have 4 formats, that is now 4x6=24 functions I would need to deal with.
I would love to have the luxury of encapsulating all the decoding/deserialization into a single function: Dim MyObject as Something = Deserialize(Of Something)(StringFromAPI, MyEncodingEnumOptions.Option42)
Many thanks in advance!
Performing a specific action depending on the type of a specific variable: that feels similar to Overloading, except that here instead of performing the action based on the type of the input variables, it should be base on the type of the output variables.
Unfortunately, it is not possible to overload the TypeName of a generic function. For example, Function MyFunction(Of T as New)(SomeParameter as String) as T and Function MyFunction(Of T as Structure)(SomeParameter as String) as T cannot coexist in the same namespace.
An alternative is to pass the expected output type as an input argument, so that regular overloading can be performed: Sub MyFunction(ByVal SomeParameter as String, ByRef OutputVar as SomeType). Each overload including a different SomeType TypeName.
The output of the "function" is stored into OutputVar, which is passed ByRef and retrieved after running the Sub:
Dim MyObject as Something = Deserialize(Of Something)(StringFromAPI, MyEncodingEnumOptions.Option42)
Becomes
Sub Deserialize(ByRef MyObject as String, ByVal MyString As String, ByVal EncodingOption As MyEncodingEnumOptions)
MyString = SomeDecoding(MyString, EncodingOption)
MyObject = SomeFunction(MyString)
End Sub
Sub Deserialize(ByRef MyObject as IList(Of String), ByVal MyString As String, ByVal EncodingOption As MyEncodingEnumOptions)
MyString = SomeDecoding(MyString, EncodingOption)
MyObject = SomeOtherFunction(MyString)
End Sub
'...
Dim MyObject as Something
Deserialize(MyObject, StringFromAPI, MyEncodingEnumOptions.Option42)
'Now MyObject has been filled with the relevant data.
An alternative is to use late binding / runtime object initilization, using Activator.CreateInstance(Of T). A typical switch over T would then look like:
Public Function GetDeserializedObject(Of T)(ByVal MyString As String) As T
Select Case GetType(T)
Case GetType(String)
Return CTypeDynamic(MyString, GetType(T)) '<-- Runtime Casting
Case Else
If Not MyString.IsDeserializable Then Throw New ArgumentException(String.Format("Unable to deserialize to a {0} object: The provided string is not valid.", GetType(T).ToString))
Select Case GetType(T)
Case GetType(IList(Of String))
Return CollectionString.ToStringList(MyString)
Case Else
Dim MyReturn As T = Activator.CreateInstance(Of T) '<-- Object instantiation to the type provided at Runtim
If TypeOf MyReturn Is BaseContainer Then '<-- Now we can use TypeOf ... Is ... which will return True for all Object derived from BaseContainer
Return Activator.CreateInstance(GetType(T), MyString)
ElseIf TypeOf MyReturn Is IList(Of BaseContainer) Then
Dim MyCollectionString As CollectionString = MyString
Return MyCollectionString.ExportToContainerList(MyReturn.GetType)
Else
Throw New ArgumentException(String.Format("Unable to deserialize to a {0} object: This type of object is not supported.", GetType(T).ToString))
End If
End Select
End Select
End Function
I want to run a Method using a variable name that is stored in a Module with a parameter:
Dim subName as String = "sub1"
Dim param as Integer = 123
sub1(param) <- I want to run this using the **subName** string
I don't want to use Select Case because the Methods are in many different modules and I don't want to maintain a select-case function.
I looked up CallByName but it seems this only works for Classes. I can't figure out how to set the object ObjectRef when it comes to Modules :
Public Function CallByName(ByVal ObjectRef As System.Object,ByVal ProcName As String,ByVal UseCallType As CallType, ByVal Args() As Object) As Object
Is there a way to do this in VB.Net?
Edit: To make it really simple, I need the equivalent of VBA's:
Application.Run module_name.sub_name param
You can use reflection to create a delegate to the methods in the Module. I would load the created delegates into a Dictionary(Of String, Action(Of Int32)).
Action(Of Int32) is chosen because it matches the signature you specified of a subroutine taking an integer parameter.
Assume you have a Module defined like this:
Module SomeModule
Public Sub Sub1(arg As Int32)
Console.WriteLine("Sub1: {0}", arg)
End Sub
Public Sub Sub2(arg As Int32)
Console.WriteLine("Sub2: {0}", arg)
End Sub
End Module
Now to create and store the delegates in a dictionary.
Private methods As New Dictionary(Of String, Action(Of Int32))
Sub LoadMethods()
Dim modType As Type = GetType(SomeModule)
Dim mi As Reflection.MethodInfo
mi = modType.GetMethod("Sub1", BindingFlags.Static Or BindingFlags.Public)
methods.Add(mi.Name, CType(mi.CreateDelegate(GetType(Action(Of Int32))), Action(Of Int32)))
mi = modType.GetMethod("Sub2", BindingFlags.Static Or BindingFlags.Public)
methods.Add(mi.Name, CType(mi.CreateDelegate(GetType(Action(Of Int32))), Action(Of Int32)))
End Sub
You can retrieve and invoke the delegate like this:
methods("Sub1")(123)
methods("Sub2")(456)
Edit: I sometimes makes things to complicated. The LoadMethods method can be done without reflection like this:
Sub LoadMethods()
methods.Add("Sub1", New Action(Of Int32)(AddressOf SomeModule.Sub1))
methods.Add("Sub2", New Action(Of Int32)(AddressOf SomeModule.Sub1))
End Sub
Edit 2: Based on edit to question and comment below.
Edit: To make it really simple, I need the equivalent of VBA's:
Application.Run module_name.sub_name param
You will need to first extract the Module type from its containing assembly based on the entered name. Then you can retrieve the MethodInfo as shown above. The following example assumes that the module is contained in the executing assembly and has minimal checks implemented. It will require you to provide the module name, method name and an array properly typed method arguments. In a real world scenario, it would probably need to take a string of the arguments and perform some type of dynamic type casting to build up the typedArgs array based on calling MethodInfo.GetParameters.
Private Shared Sub Exec(moduleName As String, methodName As String, typedArgs As Object())
Dim asm As Reflection.Assembly = Assembly.GetExecutingAssembly
Dim modType As Type = asm.GetType(String.Format("{0}.{1}", asm.GetName.Name, moduleName))
If modType IsNot Nothing Then
Dim mi As Reflection.MethodInfo
mi = modType.GetMethod(methodName, BindingFlags.Static Or BindingFlags.Public)
If mi IsNot Nothing Then
mi.Invoke(Nothing, typedArgs)
End If
End If
End Sub
Example usage: Exec("SomeModule", "Sub1", New Object() {123})
Lets say you want to call subroutine (or function) sub1 with parameter 123 with optionally given module name module1
Call example, If module name is not available (function name to invoke should be unique among project):
Dim FunctionName As String = "sub1"
Dim Param As Integer = 123
InvokeModuleFunction(FunctionNameToCall:=FunctionName, FunctionParameters:=Param)
Alternatively, If you know module name:
Dim FunctionName As String = "sub1"
Dim Param As Integer = 123
Dim ModuleName As String = "module1"
InvokeModuleFunction(FunctionNameToCall:=FileType, ModuleName:=ModuleName, FunctionParameters:=Param)
InvokeModuleFunction definition
Private Sub InvokeModuleFunction(FunctionNameToCall As String, FunctionParameters As Object, Optional ModuleName As String = Nothing)
Dim MyReflectionAssembly = Reflection.Assembly.GetExecutingAssembly()
Dim MyFunctionType As Type
If IsNothing(ModuleName) Then
'Gets function without ModuleName. FunctionName should be unique in the assembly/programm.
MyFunctionType = MyReflectionAssembly.DefinedTypes.Where(Function(x) x.DeclaredMethods.Where(Function(y) y.Name = FunctionNameToCall).Count > 0).FirstOrDefault
Else
'Gets function using ModuleName, if available
MyFunctionType = MyReflectionAssembly.DefinedTypes.Where(Function(x) x.Name = ModuleName AndAlso x.DeclaredMethods.Where(Function(y) y.Name = FunctionNameToCall).Count > 0).FirstOrDefault
End If
If Not IsNothing(MyFunctionType) Then MyFunctionType.GetMethod(FunctionNameToCall).Invoke(MyFunctionType, New Object() {FunctionParameters})
End Sub
Alternatively you can use more than one parameter in invoking.
You would need to modify the above function to allow to pass more than one parameter.
The invoke part would look like:
FunctionType.GetMethod(FunctionNameToCall).Invoke(FunctionType, New Object() {Par1, Par2, Par3})
Please see the code below:
Public Function Test()
Dim o As Object = getVariable("Integer")
If TypeOf o Is Integer Then
'Do some processing on the integer
ElseIf TypeOf o Is Decimal Then
'Do some processing on the integer
End If
End Function
Public Function getVariable(ByVal strDataType As String)
If strDataType = "Integer" Then
Return New Integer
ElseIf strDataType = "Decimal" Then
Return New Decimal
ElseIf strDataType = "Double" Then
Return New Double
End If
End Function
I suspect there is an easier way (fewer lines of code) of doing this with Reflection?
You can use Type.GetType together with Activator.CreateInstance:
Public Function getVariable(ByVal strDataType As String)
Return Activator.CreateInstance(Type.GetType(strDataType))
End Function
For strDataType you need to be using System.Int32, System.Decimal and System.Double respectively. If you want to keep it as Integer etc., you need to incorporate string translation, for example, have a Dictionary(Of String, String), with entries like ("Integer", "System.Int32").
The real question is what ars you trying to solve? Activator will work. Are trying to create a factory or an IOC container. Could you provide more detail? You could also create a dictionary were the key is of type string and then item stores delegates used to actual create the type.
I have to create a class in Visual Basic called StringWork.
Public Class StringWork
Now i wrote a shared function in the class called Working that can take a string or a string and Boolean.
Public Shared Function Working(ByVal SingleString as string, optional BValu as Boolean = true)as string
if(working(SingleString))then
'The handling of the string
else if (working(SingleValue, BValue) then
'do something else with string
end if
end function
The function I wrote is returning a string.
Can I access the string passed and edit characters in the string or change the position of characters?
You use that optional parameter to determine how you handle that string:
Public Shared Function Working(ByVal singleString as string, _
Optional bValue as Boolean = True) As String
If bValue Then
'Handle the true part manipulating the result string
Else
'Handle the false part manipulating the result string
End If
End Function
If you call this function like this:
Dim test As String = StringWork.Working("I am Spartacus")
it will call that Working function with bValue = true.
What bValue is suppose to represent isn't very clear from the code nor the post.
In VB.NET, and other .NET languages, strings are immutable. Typically, if you need to modify a string that is passed to your method, you would return the modified string. If however, you need it to modify the parameter, you can specify that it is a "ByRef" argument, in which case you will be able to set it to point to a new string object which will affect the variable that was passed into the method as a parameter. If you need a truly mutable string, you will need a character array or a StringBuilder object.
What I want to do is, based on the type of T do different opperations. Below is a simple example of my problem.
Public Shared Function Example(Of T)() As T
Dim retval As T
If TypeOf retval Is String Then
Dim myString As String = "Hello"
retval = myString
ElseIf TypeOf retval Is Integer Then
Dim myInt As Integer = 101
retval = myInt
End If
Return retval
End Function
I get the error "Value of Type 'String' Cannot be converted to 'T'" Same with the integer part. If I cast either to an object before asigning them to retval it works but I think that would defeat my purpose and be less efficient. Any Ideas? Thanks!
It's probably a bit late, but try this:
Public Shared Function CAnyType(Of T)(ByRef UTO As Object) As T
Return CType(UTO, T)
End Function
Public Shared Function ExecuteSQLstmtScalar(Of T)(ByVal strSQL As String) As T
Dim T_ReturnValue As T
' Here we have the result of a DB query '
Dim obj As Object = "Value from DB query cmd.ExecuteScalar"
Dim strReturnValue As Object = obj.ToString();
Try
Dim tReturnType As Type = GetType(T)
If tReturnType Is GetType(String) Then
Return CAnyType(Of T)(strReturnValue)
ElseIf tReturnType Is GetType(Boolean) Then
Dim bReturnValue As Boolean = Boolean.Parse(strReturnValue)
Return CAnyType(Of T)(bReturnValue)
ElseIf tReturnType Is GetType(Integer) Then
Dim iReturnValue As Integer = Integer.Parse(strReturnValue)
Return CAnyType(Of T)(iReturnValue)
ElseIf tReturnType Is GetType(Long) Then
Dim lngReturnValue As Long = Long.Parse(strReturnValue)
Return CAnyType(Of T)(lngReturnValue)
Else
MsgBox("ExecuteSQLstmtScalar(Of T): This type is not yet defined.")
End If
Catch ex As Exception
End Try
Return Nothing
End Function
(the secrect is casting your generic result to object, then casting from type Object to template type T).
PS:
You are responsible to ensure that your code works correctly with nullable types and NOT nullable types, as well as System.DbNull.Value. For example when string is NULL and return value type is Boolean (not nullable). On a sidenote, please also note that VB Nothing is NOT equal NULL, it's equal to C#'s default(T) (e.g. System.Guid.Empty for Guid)
With a generic method, T will be of exactly one type each time. Let's say that you have code calling Example(Of Integer). Now, in your mind, replace T with Integer. The resulting method will contain these lines (amongst others).
Dim retval As Integer
If TypeOf retval Is String Then
Dim myString As String = "Hello"
retval = myString
' more code follows '
Assigning a String to an integer like that will never work. Sure, that code will also never execute, since the If-block prevents that, but the code will still not compile. (As a side not, the above code will fail to compile because the TypeOf keyword is restricted to use with reference types, but that is another story)
Typically when creating generic methods, you will want to do the same thing with whatever input you get, but in a type safe manner. If you want to have different behavior for different types of input, you are usually better off by overloading the methods instead.
retVal = (T) "Hello World!"
Do retval = Ctype(Mystring, T) or retVal = Ctype(MyInt, T)
An alternative solution is encapsulate this kind of logic in a class and use VB CallByName function:
Class Aux(Of T)
Public Value As T
Private dicc As Dictionary(Of String, Object)
Sub New()
dicc = New Dictionary(Of String, Object)
dicc.Add("system.string", "hola")
dicc.Add("system.int32", 15)
dicc.Add("system.double", 15.0)
End Sub
Public Function Test() As T
Dim typeName As String = GetType(T).ToString.ToLower
If dicc.ContainsKey(typeName) Then
CallByName(Me, "Value", CallType.Set, dicc(typeName))
End If
Return Value
End Function
Protected Overrides Sub Finalize()
MyBase.Finalize()
If Not (dicc Is Nothing) Then dicc.Clear()
dicc = Nothing
End Sub
End Class