I am creating an C# application that must call subs in an Access mdb database file.
I've created a test sub in the mdb file and I can call it from C# and also pass it parameters. That all works fine, but I want to pass a result back to C#. I know I can't do this with a function, so is it possible to pass the variables by reference, then vba can change the variables and I get my result back? I tried the following, but it doesn't work. Anybody know if this is possible?
Thanks
VBA test sub:
Sub test(Byref p1 As String)
p1="bar"
MsgBox p1
End Sub
Call it from C#:
Access.Application oAccess = new Access.Application();
oAccess.Visible = true;
oAccess.OpenCurrentDatabase("d:\\tmp\\test1.mdb", false, "");
string t;
t = "foo"
oAccess.Run("test", t);
//t still equals "foo" at this point, it should be equal to "bar"
For a scalar (a simple number, string, or Boolean) the system makes a
copy of the current value and passes this copy to the called
procedure. Upon return from the called procedure the system discards
the copy. So, even if the called procedure changes the value of a
ByVal argument, there is no way for that change to propagate back to
the caller.
Take a look at this for explanation on passing scalar values. As explained on the page, callee can modify the property of the object, thereby caller can see the modified value (in case of using an object).
Another alternative, is to use a return value from the function.
EDIT:
Function test(p1 As String) as String
test = "Bar"
End Function
c#
t = "foo"
t = oAccess.Run("test", t);
Related
Does the C# compiler require a function delegate to pass a function as an argument to another function?
In Visual Basic .Net I can pass a function whose return value is of the same type as a method argument to the method itself without anything fancy. This doesn't appear possible in C# unless the method was written to accept a function delegate??
working vb:
Function DoSomething(r As DataRow())
'...
End Function
Function CallSomething()
Dim dt As DataTable
DoSomething(dt.Select(""))
End Function
C# would require the calling code to assign a variable and pass that, or that the referenced function's signature included a delegate (useful to know). Subtle difference but I was expecting that a simple method call made in one language could be just as easily made in the other since they both compile to the same IL.
here's the C# equivalent I was trying to do:
string DoSomething(ref DataRow[] r)
{ ///do something }
void CallSomething()
{
DataTable dt;
DoSomething(ref dt.Select(""));
}
What I meant by "fancy" was the use of delegates, I've used delegates for more complex scenarios, like assigning event handlers to array elements, but I was surprised they would be required for this.
Thanks for the replies. The VB code is functioning as expected, ByRef is implicit in VB, you can't pass an object managed on the heap byval. Nonetheless, here's the simplest form I can provide in VB, if you use a simple forms project w/ a single listbox and drop this code in, should work fine:
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
Dim dt As New DataTable("A really great table")
dt.Columns.Add("Column1", GetType(String))
dt.Rows.Add("Row1") : dt.Rows.Add("Row2")
PutRowsInListbox(dt.Select(""))
End Sub
Private Function PutRowsInListbox(ByRef r() As DataRow)
For i As Integer = 0 To r.Length - 1
Me.ListBox1.Items.Add(r(i).Item("Column1"))
Next
End Function
End Class
Your Vb.Net code is not equivalent to your c# code. If you want them to be equivalent, you need to remove the ref keyword on the c# code (or add ByRef on the VB.Net code).
Both visual basic and c# allows this style of programming. Please note, however, that you are not passing a function (or method) to another method here, not in Vb nor in c# - you are simply populating the DataRow array with the results of the data table's Select method.
Since you are not actually passing a method as an argument, a more readable code would be to get the results of the Select method into a variable and pass that variable into the DoSomething method - but that's more a personal preference thing.
The c# code sample you have in the question will not compile.
Here's an equivalent and compilable c# version of the Vb.Net code you have now:
void DoSomething(DataRow[] r)
{
//do something
}
void CallSomething()
{
DataTable dt = null;
DoSomething(dt.Select(""));
}
Of course, that would throw a NullReferenceException unless you actually initialize the dt with a value instead of null.
BTW, ByRef is not implicit. passing reference types does not mean passing them by Reference. That's a common misconception.
When you pass a reference type to a method, what actually is going on is that you are passing the reference itself by value. You can test it easily yourself if you try to reassign the reference inside the method - you'll still see the old value outside the method if you didn't specify ByRef.
I was just writing some quick code and noticed this complier error
Using the iteration variable in a lambda expression may have unexpected results.
Instead, create a local variable within the loop and assign it the value of the iteration variable.
I know what it means and I can easily fix it, not a big deal.
But I was wondering why it is a bad idea to use a iteration variable in a lambda?
What problems can I cause later on?
Consider this code:
List<Action> actions = new List<Action>();
for (int i = 0; i < 10; i++)
{
actions.Add(() => Console.WriteLine(i));
}
foreach (Action action in actions)
{
action();
}
What would you expect this to print? The obvious answer is 0...9 - but actually it prints 10, ten times. It's because there's just one variable which is captured by all the delegates. It's this kind of behaviour which is unexpected.
EDIT: I've just seen that you're talking about VB.NET rather than C#. I believe VB.NET has even more complicated rules, due to the way variables maintain their values across iterations. This post by Jared Parsons gives some information about the kind of difficulties involved - although it's back from 2007, so the actual behaviour may have changed since then.
Assuming you mean C# here.
It's because of the way the compiler implements closures. Using an iteration variable can cause a problem with accessing a modified closure (note that I said 'can' not 'will' cause a problem because sometimes it doesn't happen depending on what else is in the method, and sometimes you actually want to access the modified closure).
More info:
http://blogs.msdn.com/abhinaba/archive/2005/10/18/482180.aspx
Even more info:
http://blogs.msdn.com/oldnewthing/archive/2006/08/02/686456.aspx
http://blogs.msdn.com/oldnewthing/archive/2006/08/03/687529.aspx
http://blogs.msdn.com/oldnewthing/archive/2006/08/04/688527.aspx
Theory of Closures in .NET
Local variables: scope vs. lifetime (plus closures) (Archived 2010)
(Emphasis mine)
What happens in this case is we use a closure. A closure is just a special structure that lives outside of the method which contains the local variables that need to be referred to by other methods. When a query refers to a local variable (or parameter), that variable is captured by the closure and all references to the variable are redirected to the closure.
When you are thinking about how closures work in .NET, I recommend keeping these bullet points in mind, this is what the designers had to work with when they were implementing this feature:
Note that "variable capture" and lambda expressions are not an IL feature, VB.NET (and C#) had to implement these features using existing tools, in this case, classes and Delegates.
Or to put it another way, local variables can't really be persisted beyond their scope. What the language does is make it seem like they can, but it's not a perfect abstraction.
Func(Of T) (i.e., Delegate) instances have no way to store parameters passed into them.
Though, Func(Of T) do store the instance of the class the method is a part of. This is the avenue the .NET framework used to "remember" parameters passed into lambda expressions.
Well let's take a look!
Sample Code:
So let's say you wrote some code like this:
' Prints 4,4,4,4
Sub VBDotNetSample()
Dim funcList As New List(Of Func(Of Integer))
For indexParameter As Integer = 0 To 3
'The compiler says:
' Warning BC42324 Using the iteration variable in a lambda expression may have unexpected results.
' Instead, create a local variable within the loop and assign it the value of the iteration variable
funcList.Add(Function()indexParameter)
Next
For Each lambdaFunc As Func(Of Integer) In funcList
Console.Write($"{lambdaFunc()}")
Next
End Sub
You may be expecting the code to print 0,1,2,3, but it actually prints 4,4,4,4, this is because indexParameter has been "captured" in the scope of Sub VBDotNetSample()'s scope, and not in the For loop scope.
Decompiled Sample Code
Personally, I really wanted to see what kind of code the compiler generated for this, so I went ahead and used JetBrains DotPeek. I took the compiler generated code and hand translated it back to VB.NET.
Comments and variable names mine. The code was simplified slightly in ways that don't affect the behavior of the code.
Module Decompiledcode
' Prints 4,4,4,4
Sub CompilerGenerated()
Dim funcList As New List(Of Func(Of Integer))
'***********************************************************************************************
' There's only one instance of the closureHelperClass for the entire Sub
' That means that all the iterations of the for loop below are referencing
' the same class instance; that means that it can't remember the value of Local_indexParameter
' at each iteration, and it only remembers the last one (4).
'***********************************************************************************************
Dim closureHelperClass As New ClosureHelperClass_CompilerGenerated
For closureHelperClass.Local_indexParameter = 0 To 3
' NOTE that it refers to the Lambda *instance* method of the ClosureHelperClass_CompilerGenerated class,
' Remember that delegates implicitly carry the instance of the class in their Target
' property, it's not just referring to the Lambda method, it's referring to the Lambda
' method on the closureHelperClass instance of the class!
Dim closureHelperClassMethodFunc As Func(Of Integer) = AddressOf closureHelperClass.Lambda
funcList.Add(closureHelperClassMethodFunc)
Next
'closureHelperClass.Local_indexParameter is 4 now.
'Run each stored lambda expression (on the Delegate's Target, closureHelperClass)
For Each lambdaFunc As Func(Of Integer) in funcList
'The return value will always be 4, because it's just returning closureHelperClass.Local_indexParameter.
Dim retVal_AlwaysFour As Integer = lambdaFunc()
Console.Write($"{retVal_AlwaysFour}")
Next
End Sub
Friend NotInheritable Class ClosureHelperClass_CompilerGenerated
' Yes the compiler really does generate a class with public fields.
Public Local_indexParameter As Integer
'The body of your lambda expression goes here, note that this method
'takes no parameters and uses a field of this class (the stored parameter value) instead.
Friend Function Lambda() As Integer
Return Me.Local_indexParameter
End Function
End Class
End Module
Note how there is only one instance of closureHelperClass for the entire body of Sub CompilerGenerated, so there is no way that the function could print the intermediate For loop index values of 0,1,2,3 (there's no place to store these values). The code only prints 4, the final index value (after the For loop) four times.
Footnotes:
There's an implied "As of .NET 4.6.1" in this post, but in my opinion it's very unlikely that these limitations would change dramatically; if you find a setup where you can't reproduce these results please leave me a comment.
"But jrh why did you post a late answer?"
The pages linked in this post are either missing or in shambles.
There was no vb.net answer on this vb.net tagged question, as of the time of writing there is a C# (wrong language) answer and a mostly link only answer (with 3 dead links).
I'm in a situation where I am writing a method where the return value directly modifies a class which was used to supply paramaters to the method.
Computer.MatchingPassword = PasswordFinder.Find(Computer.Name, PasswordList)
Does assigning to a property of a passed parameter like this have any hazards?
Technically, your code sample doesn't assign to the parameter, since you passed a property of the Computer object.
In other words, you only gave the function "name", so assigning to a different property of the same object would have no effect.
Now, in the more general case, you are still safe. Even passing the whole object, by the time you assign the return value the method has already completed. Any changes you make won't do anything.
In other words, the following code is functionally equivalent:
string password = PasswordFinder.Find(Computer.Name, PasswordList);
Computer.MatchingPassword = password;
Clearly no problems there. This is true even if it was passed by ref, as again, the method is totally done with the object, so any changes to it won't matter.
I have a VBScript app calling COM-registered C# objects. I am able to pass in a COM object to a COM call, as well as receive either a primitive or a COM object back, but I can't do both at the same time! If I try retrieving any value back from the call while also passing in a COM object, I get the exception "Invalid procedure call or argument"
Dim foo
Set foo = Server.CreateObject("Foo")
foo.SetProperty(1)
Dim bar
Set bar = Server.CreateObject("Bar")
Dim return
Set return = bar.Do(foo)
If that last line is simply bar.Do(foo) it works fine.
Also, whether it is
Set return = bar.Do(foo)
or
return = bar.Do(foo)
causes the same error in this case.
My COM classes are classes with only methods exposed, and implementing an interface. I'm getting this error by dealing with only ints, longs, and Strings.
I'm a bit rusty in this area but if your method is returning an int or string shouldn't your code then read:
return = bar.Do(foo)
instead of
Set return = bar.Do(foo)
"return = bar.Do(foo)" should work, as long as Bar.Do is actually returning something. How is Bar.Do defined?
So strings are reference types right? My understanding is a reference to the string in the heap is passed even when you pass the string ByVal to a method.
Sooo.....
String myTestValue = "NotModified";
TestMethod(myTestValue);
System.Diagnostics.Debug.Write(myTestValue); /* myTestValue = "NotModified" WTF? */
private void TestMethod(String Value)
{
Value = "test1";
}
Alternatively
Dim myTestValue As String = "NotModified"
TestMethod(myTestValue)
Debug.Print(myTestValue) /* myTestValue = "NotModified" WTF? */
Private Sub TestMethod(ByVal Value As String)
Value = "test1"
End Sub
What am I missing? And what is going on under the hood? I would have bet my life that the value would have changed....
Reference types are passed "reference by value" in .NET. This means that assigning a different value to the actual parameter does not actually change original value (unless you use ByRef/ref). However, anything you do to change the actual object that gets passed in will change the object that the calling method refers to. For example, consider the following program:
void Main()
{
var a = new A{I=1};
Console.WriteLine(a.I);
DoSomething(a);
Console.WriteLine(a.I);
DoSomethingElse(a);
Console.WriteLine(a.I);
}
public void DoSomething(A a)
{
a = new A{I=2};
}
public void DoSomethingElse(A a)
{
a.I = 2;
}
public class A
{
public int I;
}
Output:
1
1
2
The DoSomething method assigned its a parameter to have a different value, but that parameter is just a local pointer to the location of the original a from the calling method. Changing the pointer's value did nothing to change the calling method's a value. However, DoSomethingElse actually made a change to one of the values on the referenced object.
Regardless of what the other answerers say, string is not exceptional in this way. All objects behave this way.
Where string differs from many objects is that it is immutable: there aren't any methods or properties or fields on string that you can call to actually change the string. Once a string is created in .NET, it is read-only.
When you do something like this:
var s = "hello";
s += " world";
... the compiler turns this into something like this:
// this is compiled into the assembly, and doesn't need to be set at runtime.
const string S1 = "hello";
const string S2 = " world"; // likewise
string s = S1;
s = new StringBuilder().Append(s).Append(S2).ToString();
This last line generates a new string, but S1 and S2 are still hanging around. If they are constant strings built into the assembly, they'll stay there. If they were created dynamically and have no more references to them, the garbage collector can de-reference them to free up memory. But the key is to realize that S1 never actually changed. The variable pointing to it just changed to point to a different string.
Everything is passed by value unless you specify otherwise. When you're passing a String, you're actually passing a reference by value.
For Strings, this doesn't make much difference, as Strings are immutable. Meaning you never get to modify the string you receive. For other classes, though, you can modify an object passed by value (unless, like String, it is immutable). What you can't do, and what passing by reference allows you to do, is modify the variable you're passing.
Example:
Public Class Example
Private Shared Sub ExampleByValue(ByVal arg as String)
arg = "ByVal args can be modifiable, but can't be replaced."
End Sub
Private Shared Sub ExampleByRef(ByRef arg as String)
arg = "ByRef args can be set to a whole other object, if you want."
End Sub
Public Shared Sub Main()
Dim s as String = ""
ExampleByValue(s)
Console.WriteLine(s) ''// This will print an empty line
ExampleByRef(s)
Console.WriteLine(s) ''// This will print our lesson for today
End Sub
End Class
Now, this should be used very sparingly, because by-value is the default and expected. Particularly in VB, which doesn't always make it clear when you're passing by reference, it can cause a lot of problems when some method starts unexpectedly mucking around with your variables.
All types, including reference types are passed by value by default, as in your example, which means that a copy of the reference is passed. So, so no matter what, re-assigning an object like that would have no effect when you pass it by value. You're just changing what the copy of the reference points to. You must explicitly pass by reference to achieve what you're trying to do.
It's only when you modify an object that's passed by value can the effect be seen outside of the method. Of course strings are immutable, so this doesn't really apply here.
You are passing a copy not the actual reference.
read this article from microsoft
http://msdn.microsoft.com/en-us/library/s6938f28.aspx
When you pass the string to the method, a copy of the reference is taken. Thus, Value is a whole new variable that just happens to still refer to the same string in memory.
The "test" string literal is also created as a real reference type object. It's not just a value in your source code.
When you assign "test" to Value, the reference for your Value variable is updated to refer to "test" instead of the original string. Since this reference is just a copy (as we saw in step 1), the myTestValue variable outside of the function remains unchanged and still refers to the original string.
You can get a better understanding of this by testing on a type with a property you can update. If you make a change to just the property, that change is visible outside the function. If you try to replace the entire object (as you are doing with this string), that is not visible outside the function.