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.
Related
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 have a COM application object that has a VBA method signature like:
Function DoSomething(ActivityName As String, ParamArray ActivityParams() As Variant)
I'm trying to call the function from my C# code, but I'm unsure how to pass values to the 2nd argument ActivityParams(). I should be able to pass no ActivityParams, or one, or many, so I'm guessing an array at the C# end will be required.
For now, I don't need to pass any ActivityParams values at all, for the second argument, but I probably will need to soon enough.
Is this sufficient to call the DoSomething method, if I don't have any values to pass to ActivityParams?
public override void DoSomething(MyActivity myActivity)
{
Application.DoSomething(myActivity.ToString(), null);
}
And, how would I vary it to be flexible enough to pass 0, 1 or more values?
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);
I have a C# dll exposed to vb6 via com-interop. This is all working, but I am noticing something strange when I pass an array of a custom objects from .Net into VB6.
Accessing the array from VB6 is what baffles me. If I access the array directly I have to do it like this:
Dim manager as New ObjectManager
'Access with two sets of parentheses:
msgbox manager.ReturnArrayOfObjects()(0).Name
However, if I copy the array first I can access it how I would normally expect to:
Dim manager as New ObjectManager
Dim objectArray() As CustomObject
'copy the array
objectArray = manager.ReturnArrayOfObjects
'access normally:
msgbox objectArray(0).Name
In the first case I had to use two sets of parentheses: manager.ReturnArrayOfObjects()(0).Name In the second case I could just use one set of parentheses: objectArray(0).Name
Does anyone know why this is the case? Am I doing something wrong here with the interop maybe?
Here is a quick stub/sample of the C# interop code.
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
[Guid("[Guid here...]")]
public interface IObjectManager
{
[DispId(1)]
CustomObject[] ReturnArrayOfObjects();
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[Guid("[guid here...]")]
public class ObjectManager: IObjectManager
{
public CustomObject[] ReturnArrayOfObjects()
{
return new CustomObject[] { new CustomObject(), new CustomObject() };
}
}
The class CustomObject() is also exposed to com-interop and working just fine. Please let me know if you need me to post anymore code, but I think these little snippets represent the problem well enough to begin with.
Thanks in advance for your help.
ReturnArrayOfObjects() in the C# code is a method. Your VB6 code is invoking the method, which returns the array, and then accessing the first element. The difference between this
msgbox manager.ReturnArrayOfObjects()(0).Name
and this
objectArray = manager.ReturnArrayOfObjects
msgbox objectArray(0).Name
Is that in the second, you invoke the method by itself without accessing the first element, and VB is allowing you to leave off the parentheses from the method call. Conversely, the language is not allowing you to leave off the parentheses when you directly access the first element. It's simply a language feature, it's not a "double parentheses array syntax" issue.
ReturnArrayOfObjects is a method, that must be called. In VB6, if you're calling a method and supplying no parameters, and it's the entire statement, then you can omit the parenthesis.
However, in your first example, you're calling the method, and then indexing into the array returned by that method. You need the first set of parenthesis to indicate that you're passing no parameters to the method, and then the second set of parenthesis are being used for array indexing.
It seems that my library of extension methods that was written in VB.NET has problems.
I have 2 overloaded extension methods Crop().
When i reference the lib from a VB.NET project i see them. If reference it from a C# project i can't see them.
What the hell is going on?
It works like this:
In C#, when you call a method with an out or ref parameter, the compiler requires you to repeat the out or ref keyword on the calling argument. This is because it does matter to the language whether the variable is already initialised or not.
In VB.NET, when you call a method with a ByRef parameter, the compiler does not require you to repeat ByRef on the calling argument, it just sorts it out for you. This is because it does not matter to the language whether the variable is already initialised or not.
Therefore, VB.NET can easily resolve an extension method using ByRef, because there is only one possible way in which it can be called. However, C# does not know what to do because there is no syntax for telling it whether to call the method using out or ref.
All you can do is:
Re-write your extension methods to use a ByVal parameter wherever possible.
If that is not possible, then you are stuck with calling them from C# like normal methods.
Without seeing the code, my guess is that you're missing a using statement in your C# .cs file.
//other usings...
//Extension using statement...
using MyAssembly.Extensions;
class Program {
static void Main() {
//some code
String myString = "blah";
//call the extension method now
String newString = myString.MyExtensionMethod();
}
}
But this is just a guess without seeing your code.
Hope this helps!!
My methods were using byref arguments. I changed it to byval and it worked.
It's very weird apparently. In VB projects its ok but in C# not. Apparently C# does not support extension methods with byref or it is a bug.