次の方法で共有


Why Can’t Extension methods on Value Type be curried

This is a followup to an post Extension Methods and Curried delegates. I have been recently asked if why Error CS1113: “Extension methods 'Name' defined on value type 'typename' cannot be used to create delegates” was added and what does it mean ?

Here is the sort version of the story, basically the there is no direct translation to IL that would be correct for the operation( an extension method defined on a value type to be current as an instance method and assigned to a delegate).

To try this out one can generated IL like this

IL_0006: nop

IL_0007: ldc.i4.s 10

IL_0009: stloc.0

IL_000a: ldloc.0

IL_000b: box [mscorlib]System.Int32

IL_0010: ldftn int32 Test.Extension::func(int32)

IL_0016: newobj instance void Test.d1::.ctor(object,native int)

here the value of the int passed to the extension method is something like 205XXXXX

The problem is that value type methods (with the exception of the virtuals inherited from System.Object) are typically called with a reference to the value type, not a boxed version of the value type (this allows meaningful mutation of the value type for instance). (When a non-virtual method on a boxed value type is called directly the JIT and runtime actually conspire to route the call through a thunk called an “unboxing stub” which essentially adjusts the this pointer downwards to make it look like a value type reference so we can call the real value type method implementation).

Delegates essentially force you to box any bound parameter you supply (since the .ctor signature takes an Object and internally we store and report this value as an object reference). So straightaway you have a potential semantic problem: by forcing the value type reference into a box you lose any identity (i.e. if a mutating method is called on the boxed value it will mutate the boxed copy, not the original).

But then there’s an additional problem. On the invocation side there are three potential ways a boxed value type argument could be processed:

1. The boxed value is passed in directly. That’s what you want if it’s intended to be the first argument of a static method which is typed as Object or ValueType, or if you’re calling one of the System.Object virtuals on a value type. For instance “static void someType.foo(Object)” or “override String valueType.ToString()”.

2. The boxed value is unboxed and passed in. That’s what you want if calling a static method that with a first argument typed specifically as your value type. For instance “static void someType.foo(int32)”.

3. A reference to the value type stored inside the boxed value is passed in. This is what you want if you’re calling a non-virtual instance method on the value type. E.g. “instance void valueType.Foo()”.

I believe CLR handles cases 1 and 3, but not case 2 (which is the case above). There’s a technical problem with the implementation here, This has to do with the way CLR implements delegates to get the fastest invocation speed possible.

One way I can think to overcome this problem is to create a stub our selfs. That is we could leverage the lambda implementation to create a lambda that calls the static method and then use the lambda as the delegate value.

For anyone Searching the web for this, Hope this helps :)

Comments