Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

vb.net weird extension method behaviour?

Here's a little something I discovered in vb.net which I cannot figure out, I've just got a form with a treeview on it and then the following:

Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
    treeTest.Nodes.Add("a")
    treeTest.Nodes(0).Test()
End Sub

Test is an extension method:

Imports System.Runtime.CompilerServices
Public Module ExtModule
    <Extension()>
    Public Sub Test(ByRef node As TreeNode)
    End Sub
End Module

If I use ByRef then my treeview looks like:

http://i.imgur.com/nQk0s.png

And with ByVal I get:

http://i.imgur.com/n2ZSf.png

This seems totally backwards, if I'm simply sending a reference why is the node appearing twice, while if I make a copy it only appears once?

like image 512
Levi H Avatar asked Nov 21 '12 14:11

Levi H


2 Answers

Okay, I've worked out some of what's going on.

It's got relatively little to do with extension methods per se. It's more about how VB handles ByRef in general, and some odd behaviour of TreeView.Nodes by the looks of it.

In particular, you'll get the exact same behaviour if you change this:

treeTest.Nodes(0).Test()

to:

ExtModule.Test(treeTest.Nodes(0))

... even if you remove the ExtensionAttribute.

Here's some C# code which demonstrates the same effect, without using ref parameters or extension methods at all:

using System.Drawing;
using System.Windows.Forms;

class Test
{
    static void Main()
    {
        TreeView tree = new TreeView { Nodes = { "a" } };
        Form form = new Form { Controls = { tree } };
        form.Load += delegate {
            TreeNode node = tree.Nodes[0];
            tree.Nodes[0] = node;
        };
        Application.Run(form);
    }
}

The important lines are these ones:

TreeNode node = tree.Nodes[0];
tree.Nodes[0] = node;

When your empty extension method has a ByRef parameter, your code is equivalent to the above C# code - because VB fakes "real" ByRef behaviour by using a temporary variable and then assigning back to the original property.

When your empty extension method has a ByVal parameter, your code is just equivalent to:

TreeNode node = tree.Nodes[0];
// Do nothing

... and that doesn't create a second node.

like image 107
Jon Skeet Avatar answered Nov 11 '22 15:11

Jon Skeet


I compiled a little VB example and decompiled it as C# code with Reflector. This is what I got:

treeView.Nodes.Add("a");
TreeNodeCollection VB$t_ref$S0 = treeView.Nodes;
int VB$t_i4$S0 = 0;
TreeNode VB$t_ref$S1 = VB$t_ref$S0[VB$t_i4$S0];
ref VB$t_ref$S1.Test();
VB$t_ref$S0[VB$t_i4$S0] = VB$t_ref$S1;

It does not compile. Therefore I did another test

treeView1.Nodes.Add("a");
treeView1.Nodes[0] = treeView1.Nodes[0];
treeView1.Nodes[0] = treeView1.Nodes[0];
treeView1.Nodes[0] = treeView1.Nodes[0];

Each assignment to the Nodes collection duplicates the node visually; however, the node count remains 1. This is clearly an error in the behavior of TreeView.

Note: Apparently VB allows the first parameter of an extension method to be by reference. This is awkward and can lead to much unexpected behavior. My advice: Don't use ByRef here!

like image 44
Olivier Jacot-Descombes Avatar answered Nov 11 '22 15:11

Olivier Jacot-Descombes