Introduction

Yesterday I made a post about using Clay with VB.Net. Clay works but not all functionality we have in C# work in VB.Net, which is mildly frustrating. To see if I could find a solution I posted a question on StackOverflow. But no solution yet.

The problem

This works in C#

Text
1
2
3
4
dynamic c = new ClayFactory();
        var plant = c.Plant(new {LatinName = "test"});
        Console.WriteLine(plant.LatinName);
        Console.ReadLine();
dynamic c = new ClayFactory();
        var plant = c.Plant(new {LatinName = "test"});
        Console.WriteLine(plant.LatinName);
        Console.ReadLine();

but this does not work in VB.Net

vb.net
1
2
3
4
Dim c As Object = New ClayFactory
        Dim plant = c.Plant(New With {.LatinName = "test"})
        Console.WriteLine(plant.LatinName)
        Console.ReadLine()
Dim c As Object = New ClayFactory
        Dim plant = c.Plant(New With {.LatinName = "test"})
        Console.WriteLine(plant.LatinName)
        Console.ReadLine()

I get this error message in VB.Net:

Cannot close over byref parameter
‘$arg1′ referenced in lambda ”

I’m not 100% sure how to solve this if I can even solve it. I’m guessing the VB.Net implementation of anonymous types is slightly different.

I get the error on this line:

vb.net
1
Dim plant = c.Plant(New With {.LatinName = "test"})
Dim plant = c.Plant(New With {.LatinName = "test"})

I would appreciate if someone could explain this to me.

The IL seems to be quit different.

For VB the private field Latinname is this:

.field private initonly !T0 $LatinName

For C# it is:

.field private initonly !'<latinname>j__TPar' '<latinname>i__Field'
.custom instance void [mscorlib]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [mscorlib]System.Diagnostics.DebuggerBrowsableState) = ( 01 00 00 00 00 00 00 00 ) </latinname></latinname>

And the public method Get_LatinName is this.

VB:

ce !T0 get_LatinName() cil managed
{
.custom instance void [mscorlib]System.Diagnostics.DebuggerNonUserCodeAttribute::.ctor() = ( 01 00 00 00 )
// Code size 11 (0xb)
.maxstack 1
.locals init (!T0 V_0)
IL_0000: ldarg.0
IL_0001: ldfld !0 class VB$AnonymousType_0`1::$LatinName
IL_0006: stloc.0
IL_0007: br.s IL_0009
IL_0009: ldloc.0
IL_000a: ret
} // end of method VB$AnonymousType_0`1::get_LatinName

C#:

.method public hidebysig specialname instance !'j__TPar'
get_LatinName() cil managed
{
// Code size 11 (0xb)
.maxstack 1
.locals init (!'j__TPar' V_0)
IL_0000: ldarg.0
IL_0001: ldfld !0 class 'f__AnonymousType0`1'j__TPar'>::'i__Field'
IL_0006: stloc.0
IL_0007: br.s IL_0009
IL_0009: ldloc.0
IL_000a: ret
} // end of method 'f__AnonymousType0`1'::get_LatinName

And these are the main methods:

VB:

.method public static void Main() cil managed
{
.entrypoint
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )
// Code size 90 (0x5a)
.maxstack 7
.locals init ([0] object c,
[1] object plant,
[2] class VB$AnonymousType_0`1 VB$t_ref$S0,
[3] object[] VB$t_array$S0)
IL_0000: nop
IL_0001: newobj instance void [ClaySharp]ClaySharp.ClayFactory::.ctor()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: ldnull
IL_0009: ldstr "Plant"
IL_000e: ldc.i4.1
IL_000f: newarr [mscorlib]System.Object
IL_0014: stloc.3
IL_0015: ldloc.3
IL_0016: ldc.i4.0
IL_0017: ldstr "test"
IL_001c: newobj instance void class VB$AnonymousType_0`1::.ctor(!0)
IL_0021: stelem.ref
IL_0022: nop
IL_0023: ldloc.3
IL_0024: ldnull
IL_0025: ldnull
IL_0026: ldnull
IL_0027: call object [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.NewLateBinding::LateGet(object,
class [mscorlib]System.Type,
string,
object[],
string[],
class [mscorlib]System.Type[],
bool[])
IL_002c: call object [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::GetObjectValue(object)
IL_0031: stloc.1
IL_0032: ldloc.1
IL_0033: ldnull
IL_0034: ldstr "LatinName"
IL_0039: ldc.i4.0
IL_003a: newarr [mscorlib]System.Object
IL_003f: ldnull
IL_0040: ldnull
IL_0041: ldnull
IL_0042: call object [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.NewLateBinding::LateGet(object,
class [mscorlib]System.Type,
string,
object[],
string[],
class [mscorlib]System.Type[],
bool[])
IL_0047: call object [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::GetObjectValue(object)
IL_004c: call void [mscorlib]System.Console::WriteLine(object)
IL_0051: nop
IL_0052: call string [mscorlib]System.Console::ReadLine()
IL_0057: pop
IL_0058: nop
IL_0059: ret
} // end of method Module1::Main

C#:

.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 299 (0x12b)
.maxstack 10
.locals init ([0] object c,
[1] object plant,
[2] class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo[] CS$0$0000)
IL_0000: nop
IL_0001: newobj instance void [ClaySharp]ClaySharp.ClayFactory::.ctor()
IL_0006: stloc.0
IL_0007: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1f__AnonymousType0`1',object>> ConsoleApplication2.Program/'o__SiteContainer0'::'p__Site1'
IL_000c: brtrue.s IL_004c
IL_000e: ldc.i4.0
IL_000f: ldstr "Plant"
IL_0014: ldnull
IL_0015: ldtoken ConsoleApplication2.Program
IL_001a: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_001f: ldc.i4.2
IL_0020: newarr [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
IL_0025: stloc.2
IL_0026: ldloc.2
IL_0027: ldc.i4.0
IL_0028: ldc.i4.0
IL_0029: ldnull
IL_002a: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags,
string)
IL_002f: stelem.ref
IL_0030: ldloc.2
IL_0031: ldc.i4.1
IL_0032: ldc.i4.1
IL_0033: ldnull
IL_0034: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags,
string)
IL_0039: stelem.ref
IL_003a: ldloc.2
IL_003b: call class [System.Core]System.Runtime.CompilerServices.CallSiteBinder [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.Binder::InvokeMember(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags,
string,
class [mscorlib]System.Collections.Generic.IEnumerable`1,
class [mscorlib]System.Type,
class [mscorlib]System.Collections.Generic.IEnumerable`1)
IL_0040: call class [System.Core]System.Runtime.CompilerServices.CallSite`1 class [System.Core]System.Runtime.CompilerServices.CallSite`1f__AnonymousType0`1',object>>::Create(class [System.Core]System.Runtime.CompilerServices.CallSiteBinder)
IL_0045: stsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1f__AnonymousType0`1',object>> ConsoleApplication2.Program/'o__SiteContainer0'::'p__Site1'
IL_004a: br.s IL_004c
IL_004c: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1f__AnonymousType0`1',object>> ConsoleApplication2.Program/'o__SiteContainer0'::'p__Site1'
IL_0051: ldfld !0 class [System.Core]System.Runtime.CompilerServices.CallSite`1f__AnonymousType0`1',object>>::Target
IL_0056: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1f__AnonymousType0`1',object>> ConsoleApplication2.Program/'o__SiteContainer0'::'p__Site1'
IL_005b: ldloc.0
IL_005c: ldstr "test"
IL_0061: newobj instance void class 'f__AnonymousType0`1'::.ctor(!0)
IL_0066: callvirt instance !3 class [mscorlib]System.Func`4f__AnonymousType0`1',object>::Invoke(!0,
!1,
!2)
IL_006b: stloc.1
IL_006c: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1> ConsoleApplication2.Program/'o__SiteContainer0'::'p__Site2'
IL_0071: brtrue.s IL_00b6
IL_0073: ldc.i4 0x100
IL_0078: ldstr "WriteLine"
IL_007d: ldnull
IL_007e: ldtoken ConsoleApplication2.Program
IL_0083: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_0088: ldc.i4.2
IL_0089: newarr [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
IL_008e: stloc.2
IL_008f: ldloc.2
IL_0090: ldc.i4.0
IL_0091: ldc.i4.s 33
IL_0093: ldnull
IL_0094: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags,
string)
IL_0099: stelem.ref
IL_009a: ldloc.2
IL_009b: ldc.i4.1
IL_009c: ldc.i4.0
IL_009d: ldnull
IL_009e: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags,
string)
IL_00a3: stelem.ref
IL_00a4: ldloc.2
IL_00a5: call class [System.Core]System.Runtime.CompilerServices.CallSiteBinder [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.Binder::InvokeMember(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags,
string,
class [mscorlib]System.Collections.Generic.IEnumerable`1,
class [mscorlib]System.Type,
class [mscorlib]System.Collections.Generic.IEnumerable`1)
IL_00aa: call class [System.Core]System.Runtime.CompilerServices.CallSite`1 class [System.Core]System.Runtime.CompilerServices.CallSite`1>::Create(class [System.Core]System.Runtime.CompilerServices.CallSiteBinder)
IL_00af: stsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1> ConsoleApplication2.Program/'o__SiteContainer0'::'p__Site2'
IL_00b4: br.s IL_00b6
IL_00b6: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1> ConsoleApplication2.Program/'o__SiteContainer0'::'p__Site2'
IL_00bb: ldfld !0 class [System.Core]System.Runtime.CompilerServices.CallSite`1>::Target
IL_00c0: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1> ConsoleApplication2.Program/'o__SiteContainer0'::'p__Site2'
IL_00c5: ldtoken [mscorlib]System.Console
IL_00ca: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_00cf: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1> ConsoleApplication2.Program/'o__SiteContainer0'::'p__Site3'
IL_00d4: brtrue.s IL_0109
IL_00d6: ldc.i4.0
IL_00d7: ldstr "LatinName"
IL_00dc: ldtoken ConsoleApplication2.Program
IL_00e1: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_00e6: ldc.i4.1
IL_00e7: newarr [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
IL_00ec: stloc.2
IL_00ed: ldloc.2
IL_00ee: ldc.i4.0
IL_00ef: ldc.i4.0
IL_00f0: ldnull
IL_00f1: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags,
string)
IL_00f6: stelem.ref
IL_00f7: ldloc.2
IL_00f8: call class [System.Core]System.Runtime.CompilerServices.CallSiteBinder [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.Binder::GetMember(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags,
string,
class [mscorlib]System.Type,
class [mscorlib]System.Collections.Generic.IEnumerable`1)
IL_00fd: call class [System.Core]System.Runtime.CompilerServices.CallSite`1 class [System.Core]System.Runtime.CompilerServices.CallSite`1>::Create(class [System.Core]System.Runtime.CompilerServices.CallSiteBinder)
IL_0102: stsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1> ConsoleApplication2.Program/'o__SiteContainer0'::'p__Site3'
IL_0107: br.s IL_0109
IL_0109: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1> ConsoleApplication2.Program/'o__SiteContainer0'::'p__Site3'
IL_010e: ldfld !0 class [System.Core]System.Runtime.CompilerServices.CallSite`1>::Target
IL_0113: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1> ConsoleApplication2.Program/'o__SiteContainer0'::'p__Site3'
IL_0118: ldloc.1
IL_0119: callvirt instance !2 class [mscorlib]System.Func`3::Invoke(!0,
!1)
IL_011e: callvirt instance void class [mscorlib]System.Action`3::Invoke(!0,
!1,
!2)
IL_0123: nop
IL_0124: call string [mscorlib]System.Console::ReadLine()
IL_0129: pop
IL_012a: ret
} // end of method Program::Main

The C# and VB version are nothing alike. Seems like the C# version does a lot more. So there goes the theory C# and VB.Net compile to the same IL.

I also tried debugging Clay to see which line was causing the problem on their side.

The problem is that it is building a whole expressiontree and that it is difficult to say where it actually fails.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) {
            Logger.Log(LogLevel.Debug, null, "BindInvokeMember");
 
            var argValues = Expression.NewArrayInit(typeof(object), args.Select(x => Expression.Convert(x.Expression, typeof(Object))));
            var argNames = Expression.Constant(binder.CallInfo.ArgumentNames, typeof(IEnumerable<string>));
            var argNamedEnumerable = Expression.Call(typeof(Arguments).GetMethod("From"), argValues, argNames);
 
            var binderDefault = binder.FallbackInvokeMember(this, args);
 
            var missingLambda = Expression.Lambda(Expression.Call(
                GetClayBehavior(),
                IClayBehavior_InvokeMemberMissing,
                Expression.Lambda(binderDefault.Expression),
                GetLimitedSelf(),
                Expression.Constant(binder.Name, typeof(string)),
                argNamedEnumerable));
 
            var call = Expression.Call(
                GetClayBehavior(),
                IClayBehavior_InvokeMember,
                missingLambda,
                GetLimitedSelf(),
                Expression.Constant(binder.Name, typeof(string)),
                argNamedEnumerable);
 
            var dynamicSuggestion = new DynamicMetaObject(
                call, BindingRestrictions.GetTypeRestriction(Expression, LimitType).Merge(binderDefault.Restrictions));
 
            return binder.FallbackInvokeMember(this, args, dynamicSuggestion);
        }
public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) {
            Logger.Log(LogLevel.Debug, null, "BindInvokeMember");

            var argValues = Expression.NewArrayInit(typeof(object), args.Select(x => Expression.Convert(x.Expression, typeof(Object))));
            var argNames = Expression.Constant(binder.CallInfo.ArgumentNames, typeof(IEnumerable<string>));
            var argNamedEnumerable = Expression.Call(typeof(Arguments).GetMethod("From"), argValues, argNames);

            var binderDefault = binder.FallbackInvokeMember(this, args);

            var missingLambda = Expression.Lambda(Expression.Call(
                GetClayBehavior(),
                IClayBehavior_InvokeMemberMissing,
                Expression.Lambda(binderDefault.Expression),
                GetLimitedSelf(),
                Expression.Constant(binder.Name, typeof(string)),
                argNamedEnumerable));

            var call = Expression.Call(
                GetClayBehavior(),
                IClayBehavior_InvokeMember,
                missingLambda,
                GetLimitedSelf(),
                Expression.Constant(binder.Name, typeof(string)),
                argNamedEnumerable);

            var dynamicSuggestion = new DynamicMetaObject(
                call, BindingRestrictions.GetTypeRestriction(Expression, LimitType).Merge(binderDefault.Restrictions));

            return binder.FallbackInvokeMember(this, args, dynamicSuggestion);
        }

I think it bombs out on the binder.FallBackInvokeMember. And that just invokes the expressiontree.

But nothing on MSDN seems to suggest this is not compatible with VB.Net or that they have to be carefull about something.

Conclusion

For the moment I am at a lose if you know someone that can help then please do so.