For example if I have a function call like below
Function callingMe()
Exit Function
and my Function call is like below
SomeObj.callingMe('1','2','3','4','5') // Variable number of arguments
Inside callingMe() function, I want to know arguments passed i.e 1,2,3,4,5 and the Object i.e SomeObj in above case.
The method declaration needs to be explicit, to be able to accept n parameters.
In this case, ParamArray can be used.
EDIT: The name of the variable cannot be determined, as the name is immaterial to the runtime. You can refer to the instance of the class using Me keyword.
EDIT2: Assuming that someObj is an instance of a class Person, which has a property named FirstName, you could use Me.FirstName inside callingMe (which is a method in Person class).
Sub Main
dim someObj as Person
someObj = new Person
someObj.FirstName = "Hello"
someObj.callingMe("1","2","3")
End Sub
' Define other methods and classes here
Class Person
Private firstNameValue As String
Public Property FirstName() As String
Get
Return firstNameValue
End Get
Set(ByVal value As String)
firstNameValue = value
End Set
End Property
Function callingMe(paramarray args() as string)
Console.WriteLine(me.FirstName + "," + args(0))
End Function
End Class
You can use a ParamArray to accept a variable number of arguments.
Update according to your question in the comments: To get a reference to the caller you must pass it in as a separate argument. Please note that all fixed parameters must come before the ParamArray
Function callingMe(caller As Object, ParamArray parameters() As String)
For i as Integer = 0 to parameters.Length -1
Console.WriteLine(parameters(i))
Next i
End Function
// Method call:
SomeObj.callingMe(Me, "1", "2", "3", ...)
Related
I am using a function to modify a series of strings, passing them ByRef as arguments to the modifying function. The caller's string variables are all modified as expected but the one argument which is a class property does not change, should this be possible?
The essentials of the class are:-
Private cRptRef As String
Public Property Get TestRefID() As String
TestRefID = cRptRef
End Property
Public Property Let TestRefID(Test_Ref As String)
cRptRef = Test_Ref
End Property
The function for modifying the strings has the following declaration
Public Function GetTestFileNames(ByRef hdrFile As String, _
ByRef calFile As String, _
ByRef dataFile As String, _
ByRef testRef As String _
) As Boolean
The call to GetTestFilenames is as follows:
If GetTestFileNames(HEADERpath, CALpath, RAWDATApath, _
ref) = False Then
All the string arguments are declared as global strings and are empty ("") before the call. After the call they typicaly have content like "d:{path to file{filename.csv}.
So all these statements in the function populate the target strings OK.
hdrFile = Replace(userFile, "##", PT_Rpt.Info.FindNode(TEST_REF_HDRsuffix).data, , , vbTextCompare)
dataFile = Replace(userFile, "##", PT_Rpt.Info.FindNode(TEST_REF_DATAsuffix).data, , , vbTextCompare)
calFile = Replace(userFile, "##", PT_Rpt.Info.FindNode(TEST_REF_CALsuffix).data, , , vbTextCompare)
But this statement fails to assign anything to its target string
testRef = Mid(userFile, InStrRev(userFile, Application.PathSeparator) + 1)
testRef = Left(testRef, InStrRev(testRef, "_") - 1)
Debug.Print "Class.TestRefID="; testRef
The Debug.Print statement prints the expected string, but the external reference is not affected. Is this something to do with it being a property?
The target string being Class.TestRefID in place of the testRef argument.
If I replace the class property in the argument list with a standard string variable, and then assign that to the class property then it I get the expected result, which seems unnecessary work.
Is there something I'm missing or is this not possible in VBA?
A member access expression is an expression that must first be evaluated by VBA before its result can be passed around.
If I have a Class1 module like this:
Option Explicit
Public Foo As String
And then a quick caller procedure:
Sub test()
With New Class1
bla .Foo
Debug.Print .Foo
End With
End Sub
Sub bla(ByRef bar As String)
bar = "huh"
End Sub
The test procedure will output an empty string.
The reason for this is because when you pass a member to a ByRef parameter of a procedure in VBA, you're not passing a reference to the member - you're passing a reference to the value held by that member.
So the member access expression is evaluated, evaluates to "", so "" is passed ByRef to the procedure, which assigns it to "huh", but the caller isn't holding a reference to the "" value, so it never gets to see the "huh" string that was assigned.
If I replace the class property in the argument list with a standard string variable, and then assign that to the class property then it I get the expected result, which seems unnecessary work
It's not unnecessary work, it's mandated, otherwise nothing is holding a reference to the result of the member expression.
Then again the real problem is a design issue, pointed out by Warcupine: the function doesn't want to byref-return 4 values, it wants to take a reference to this object, and assign its properties.
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 have read that String was a "reference type", unlike integers. MS website
I tried to test its behavior.
Sub Main()
Dim s As New TheTest
s.TheString = "42"
Dim z As String = s.GimmeTheString
z = z & "000"
Dim E As String = s.TheString
s.GimmeByref(z)
end sub
Class TheTest
Public TheString As String
Public Function GimmeTheString() As String
Return TheString
End Function
Public Sub GimmeByref(s As String)
s = TheString
End Sub
End Class
So I expected :
z is same reference as TheString, thus TheString would be set to "42000"
Then Z is modified by reference by GimmeByref thus Z is set to whatever TheString is
Actual result:
Z="42000"
E="42"
TheString="42"
What point am I missing?
I also tried adding "ByRef" in GimmeByRef : yes obviously the GimmeByRef does work as expected, but it also does if I put everything as Integer, which are said to be "Value type".
Is there any actual difference between those types?
The confusion comes about because regardless of type, argument passing in VB is pass by value by default.
If you want to pass an argument by reference, you need to specify the argument type as ByRef:
Public Sub GimmeByref(ByRef s As String)
You also need to understand the difference between mutating a value and re-assigning a variable. Doing s = TheString inside the method doesn’t mutate the value of the string, it reassigns s. This can obviously be done regardless of whether a type is a value or reference type.
The difference between value and reference types comes to bear when modifying the value itself, not a variable:
obj.ModifyMe()
Strings in .NET are immutable and thus don’t possess any such methods (same as integers). However, List(Of String), for instance, is a mutable reference type. So if you modify an argument of type List(Of String), even if it is passed by value, then the object itself is modified beyond the scope of the method.
Strings are immutable, every time you do a change it creates a new "reference" like if New was called.
A String object is called immutable (read-only), because its value
cannot be modified after it has been created. Methods that appear to
modify a String object actually return a new String object that
contains the modification. Ref
Your code basically does something like this:
Sub Main()
Dim a, b As String
a = "12"
b = a
a = a & "13"
Console.WriteLine(a) ' 1213
Console.WriteLine(b) ' 12
Console.ReadLine()
End Sub
Hi I'm rather new to VBA I need to create an object with relativelly complicated Getter and Setter. To do this I am constantly checking with MSDN but clearly I am not understanding something because VBE keep highlighting lines starting and closing: Property (it appaently needs Get or Let??), Get(it apparently needs identifier), Let(it apparently needs identifier as well).
But I am trying to follow more concise notation where Get and Let methods are within a Property Statement which is used by Microsoft in its examples(see link above).
Can someone tell me where is my syntax wrong(or Microsoft's Documentation for that matter)???
Thank you
Private Matrix() As Vector
Property Transition()
Public Get(Old_S As String, New_S As String, Period As Integer) As Double
' Some Code
Return Matrix(Column, Row).Value(Period)
End Get
Public Let(Old_S As String, New_S As String, Vector_String As String)
' Some Code
Matrix(Row, Column).Value = Vector_String
End Let
End Property
You are reading the documentation for VB.NET. That's why you are confused. The syntax for properties in VBA is different. In VBA, the Get and Let for a property are not grouped together. They need to be listed separately, essentially like two separate methods:
Private mMyProperty As String
Public Property Get MyProperty() As String
MyProperty = mMyProperty
End Property
Public Property Let Transition(Value As String)
mMyProperty = Value
End Property
For VBA reference material, try starting here.
Your issue is that you are reading .Net help files! :)
Assuming you have a valid Vector class, your properties need to bde defined like this:
Private Matrix() As Vector
Public Property Get Transition(Old_S As String, New_S As String, Period As Integer) As String
' Some Code
Transition = Matrix(Column, Row).Value(Period)
End Property
Public Property Let Transition(Old_S As String, New_S As String, Period As Integer, Vector_String As String)
' Some Code
Matrix(Row, Column).Value = Vector_String
End Property
Note that the argument list for both procedures must match except that the Letter has an additional argument of the same type that the Getter returns.
It is also slightly unusual to have a Getter property that accepts arguments in VBA - that would more usually be implemented as a method.
Looks like you're using VB.Net syntax in VBA. That's not going to work, as they're entirely different languages. Here is a link to the proper documentation for the VBA Property Keyword.
Here is how you would write it in VBA.
Private Matrix() As Vector
Public Property Get MatrixValue(Old_S As String, New_S As String, Period As Integer) As Double
' Some Code
MatrixValue = Matrix(Column, Row).Value(Period)
End Property
Public Property Let MatrixValue(Old_S As String, New_S As String, Vector_String As String)
' Some Code
Matrix(Row, Column).Value = Vector_String
End Property
But you're going to have trouble with the Get property. You can pass parameters into Get, but it's not exactly intuitive. I think what you're really looking for is a function.
Public Function GetMatrixValue(Old_S As String, New_S As String, Period As Integer) As Double
' Some Code
MatrixValue = Matrix(Column, Row).Value(Period)
End Function
I am having a very weird situation in VS 2012 RC VB.NET project targeting .NET 2.0. For some reason the property's Set method is called in addition to its Get method:
This works as expected:
Dim _searchparray = New Byte() {37, 115, ...}
Dim rep() As Byte = _opt.ReplaceBytes
If Arrays.CompareTo(rep, _searchparray, 1, False) = -1 AndAlso _opt.SearchMatchPlaceholderInReplaceBytes Then ...
That is _opt.ReplaceBytes's Get method is called only once, and it's Set method is not called.
But this does not work:
Dim _searchparray = New Byte() {37, 115, ...}
If Arrays.CompareTo(_opt.ReplaceBytes, _searchparray, 1, False) = -1 AndAlso _opt.SearchMatchPlaceholderInReplaceBytes Then ...
Here, first _opt.ReplaceBytes's Get method is called, then Arrays.CompareTo returns and THEN _opt.ReplaceBytes's Set method is called! Why? The call stack indicates that the caller is the last line in the sample above! But where does it set the property? It cannot be in Arrays.CompareTo because the Set method is called after the function returned a value, and it cannot be set via _opt.SearchMatchPlaceholderInReplaceBytes's Get method either, because its Get method returns the value of the underlying field and does nothing else!
Does any one have an explanation for this weird behavior?
Thanks.
Here's the entire sample project that demonstrates this:
Imports System.Runtime.CompilerServices
Module Module1
Sub Main()
Dim _opt As New Opts
Dim _searchparray = New Byte() {37, 115}
If Arrays.CompareTo(_opt.ReplaceBytes, _searchparray, 1, False) = -1 AndAlso _opt.SearchMatchPlaceholderInReplaceBytes Then
Console.WriteLine("0")
End If
Console.WriteLine("1")
End Sub
End Module
Module Arrays
<Extension()> _
Friend Function CompareTo(Of T As IEquatable(Of T))(ByRef SearchArray() As T, ByRef AnotherArray() As T, ByRef aWildCardElement As T, Optional aUseWildcards As Boolean = True) As Integer
Dim min As Integer = If(SearchArray.Length < AnotherArray.Length, SearchArray.Length, AnotherArray.Length) - 1
If aUseWildcards AndAlso aWildCardElement IsNot Nothing Then
For i = 0 To min
If SearchArray(i).Equals(aWildCardElement) Then Continue For
If Not SearchArray(i).Equals(AnotherArray(i)) Then Return i
Next
Else
For i = 0 To min
If Not SearchArray(i).Equals(AnotherArray(i)) Then Return i
Next
End If
If SearchArray.Length = AnotherArray.Length Then
Return -1
Else
Return min + 1
End If
End Function
End Module
Public Class Opts
Private _ReplaceBytes() As Byte = New Byte() {}
<Xml.Serialization.XmlIgnore()> _
Public Property ReplaceBytes As Byte()
Get
Return _ReplaceBytes
End Get
Set(ByVal value As Byte())
_ReplaceBytes = value
End Set
End Property
Private _SearchMatchPlaceholderInReplaceBytes As Boolean = False
Public Property SearchMatchPlaceholderInReplaceBytes() As Boolean
Get
Return _SearchMatchPlaceholderInReplaceBytes 'Set breakpoint here
End Get
Set(ByVal value As Boolean)
'Set breakpoint here too
_SearchMatchPlaceholderInReplaceBytes = value
End Set
End Property
End Class
Namespace Global.System.Runtime.CompilerServices
<AttributeUsage((AttributeTargets.Method Or (AttributeTargets.Class Or AttributeTargets.Assembly))), System.Reflection.Obfuscation(ApplyToMembers:=True, Exclude:=True)> _
Public NotInheritable Class ExtensionAttribute
Inherits Attribute
Public Sub New()
End Sub
End Class
End Namespace
This is an interaction between the ByRef declaration and passing a property as the argument. This is forbidden in C# but the VB.NET compiler works around the problem.
By declaring the argument ByRef, you tell the compiler that you might modify the passed object reference. Which is fine if you pass a local variable as the method argument, that local variable gets updated when your code assigns the argument. But this is a problem when you pass a property, such an assignment would have to call the property setter. Which in turn invalidates the passed argument. Which can cause a very difficult to diagnose bug.
The C# compiler just forbids this due to the bug possibilities. The VB.NET compiler however works around it by ensuring that the setter gets called after the method stops executing. Which is exactly what you saw with the debugger. Trouble is, it always calls the setter, even if you didn't modify the argument.
The workaround is obvious, using ByRef is just a bug. Your method does not actually assign the SearchArray argument. The argument needs to be ByVal.
It seems that in VB.NET, when you pass a property of Array type by reference, it is copied back at the end of the function. This makes sense because passing it ByRef means that the reference might have changed inside the function. Since it's a property that has been passed by reference, we affect the (possibly changed) reference to the property's setter.
The solution would be to pass it by value instead (ByVal). You have no need to pass it by reference in your code. In fact, in most cases, it is better to pass it by value than by reference. Only use ByRef if a parameter also acts as a return value.