use*_*409 4 .net c# cil reflection.emit
我有几个月前写的这个框架,它生成一个用于调用此性能服务的类.框架的消费者使用方法创建接口,使用属性进行批注,并调用工厂方法,该方法创建可用于调用此性能服务的接口的实现.该服务仅支持两个数据字符串和long.我使用带有可收集程序集的reflect emit来生成实现接口的类.
一切都运行良好,但今天有人告诉我,当他们试图传入一个将转换为字符串的枚举时,他们正在获得AV.在代码中,检查类型是否为值类型,如果是,则按地址(ldarga或ldflda,具体取决于消费者创建的接口),然后调用ToString.所以我创建了一个小调试应用程序,我看到C#编译器会打开枚举,然后在盒装枚举上调用ToString.
所以我有点困惑.我处理价值类型的方式不正确吗?IL是C#编译器为枚举生成toString的正确方法吗?还有其他特殊情况吗?
更新答案:所以看起来我需要查看值类型是否实现tostring以及它是否不是框.对于值类型,我猜这适用于对象方法,tostring,gethashcode,equals.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection.Emit;
using System.Reflection;
namespace ConsoleApplication15
{
public struct H
{
}
class Program
{
static void Main(string[] args)
{
//Test<AttributeTargets>(AttributeTargets.ReturnValue); //-- fails
//Test<int>(10); //-- works
// TestBox<AttributeTargets>(AttributeTargets.ReturnValue); //-- works
//Test<H>(new H()); // fails
TestCorrect<H>(new H()); // works
TestCorrect<int>(10); // works
Console.ReadLine();
}
private static void TestCorrect<T>(T t)
where T : struct
{
MethodInfo method = typeof(T).GetMethod(
"ToString",
BindingFlags.Public | BindingFlags.Instance,
null,
Type.EmptyTypes,
null);
var m = new DynamicMethod("x", typeof(string), new[] { typeof(T) });
var i = m.GetILGenerator();
if (method.DeclaringType == typeof(T))
{
i.Emit(OpCodes.Ldarga, 0);
i.Emit(OpCodes.Call, method);
}
else
{
i.Emit(OpCodes.Ldarg_0);
i.Emit(OpCodes.Box, typeof(T));
i.Emit(OpCodes.Callvirt, method);
}
i.Emit(OpCodes.Ret);
string result = (m.CreateDelegate(typeof(Func<T, string>)) as Func<T, string>)(t);
Console.WriteLine(result);
}
private static void Test<T>(T t)
where T : struct
{
MethodInfo method = typeof(T).GetMethod(
"ToString",
BindingFlags.Public | BindingFlags.Instance,
null,
Type.EmptyTypes,
null);
var m = new DynamicMethod("x", typeof(string), new[] { typeof(T) });
var i = m.GetILGenerator();
i.Emit(OpCodes.Ldarga, 0);
i.Emit(OpCodes.Call, method);
i.Emit(OpCodes.Ret);
string result = (m.CreateDelegate(typeof(Func<T, string>)) as Func<T, string>)(t);
Console.WriteLine(result);
}
private static void TestBox<T>(T t)
where T : struct
{
// this is how the C# compiler call to string on enum.
MethodInfo method = typeof(T).GetMethod(
"ToString",
BindingFlags.Public | BindingFlags.Instance,
null,
Type.EmptyTypes,
null);
var m = new DynamicMethod("x", typeof(string), new[] { typeof(T) });
var i = m.GetILGenerator();
i.Emit(OpCodes.Ldarg_0);
i.Emit(OpCodes.Box, typeof(T));
i.Emit(OpCodes.Callvirt, method);
i.Emit(OpCodes.Ret);
string result = (m.CreateDelegate(typeof(Func<T, string>)) as Func<T, string>)(t);
Console.WriteLine(result);
}
}
}
Run Code Online (Sandbox Code Playgroud)
考虑:
using System;
class Program
{
static void Main()
{
TestFoo(Foo.A);
TestEnum(Foo.B);
TestGenerics(Foo.C);
}
static string TestFoo(Foo foo)
{
return foo.ToString();
}
static string TestEnum(Enum foo)
{
return foo.ToString();
}
static string TestGenerics<T>(T foo)
{
return foo.ToString();
}
}
enum Foo
{
A, B, C
}
Run Code Online (Sandbox Code Playgroud)
这将生成IL:
.class private auto ansi sealed Foo
extends [mscorlib]System.Enum
{
.field public static literal valuetype Foo A = int32(0)
.field public static literal valuetype Foo B = int32(1)
.field public static literal valuetype Foo C = int32(2)
.field public specialname rtspecialname int32 value__
}
Run Code Online (Sandbox Code Playgroud)
请注意,Foo它不会覆盖ToString()。编译器可能会,但是:不会。这是看到一个框单主要原因-如果struct没有override一个object.方法,你不能调用这个方法没有拳击了。简单如。嗯,不是很简单-编译器也可以使用受限的电话,这是推迟到JIT最终决定:如果类型重写的方法,它会调用它直接-否则会框。这正是编译器不会在两种情况下,它是不是已经引用类型发射(注:任何通过为Enum已装箱; Enum被引用类型):
.method private hidebysig static string TestFoo(valuetype Foo foo) cil managed
{
.maxstack 8
L_0000: ldarga.s foo
L_0002: constrained. Foo
L_0008: callvirt instance string [mscorlib]System.Object::ToString()
L_000d: ret
}
# NOTE: in this example, foo is **already** boxed before it comes in, hence
# no attempt at constrained-call
.method private hidebysig static string TestEnum(class [mscorlib]System.Enum foo) cil managed
{
.maxstack 8
L_0000: ldarg.0
L_0001: callvirt instance string [mscorlib]System.Object::ToString()
L_0006: ret
}
.method private hidebysig static string TestGenerics<T>(!!T foo) cil managed
{
.maxstack 8
L_0000: ldarga.s foo
L_0002: constrained. !!T
L_0008: callvirt instance string [mscorlib]System.Object::ToString()
L_000d: ret
}
Run Code Online (Sandbox Code Playgroud)
现在,可能是JIT和CLI在那里工作了一些伏都教徒,尽管存在过载,但仍使受约束的呼叫工作了,但是:如果没有,至少(希望)解释了为什么装箱,并证明了这一点。编译器确实非常努力地使它不装箱(通过受约束的调用)。
小智 5
枚举类型不会覆盖其ToString()方法,因此对于任何枚举类型e,都会e.ToString()解析为Enum.ToString.此方法在引用类型(Enum是引用类型)上定义,因此要调用此方法,隐式this参数必须是盒装值.
大多数其他值类型(例如int,确实)ToString直接在值类型本身上提供重写方法.
从规格:
I.12.1.6.2.4调用方法
处理值类型的静态方法与普通类上的静态方法的处理方式没有区别:使用
call带有元数据标记的指令,指定值类型作为方法的类.值类型支持非静态方法(即实例和虚方法),但它们会得到特殊处理.引用类型(而不是值类型)上的非静态方法需要一个this指针作为该类的实例.这对引用类型有意义,因为它们具有标识,并且this指针表示该标识.但是,值类型仅在装箱时才具有标识.要解决此问题,值类型的非静态方法上的this指针是值类型的byref参数,而不是普通的by-value参数.可以通过以下方式调用值类型的非静态方法:
对于值类型的未装箱实例,确切类型是静态已知的.该
call指令可用于调用该函数,作为第一个参数(this指针)传递实例的地址.与call指令一起使用的元数据标记应将值类型本身指定为方法的类.给定值类型的盒装实例,有三种情况需要考虑:
在值类型本身上引入的实例或虚方法:取消实例化实例,并使用值类型作为方法的类直接调用方法.
从基类继承虚拟方法:使用
callvirt指令和指定的方法System.Object,System.ValueType或System.Enum类作为合适的.由值类型实现的接口上的虚方法:使用
callvirt指令并在接口类型上指定方法.