gpt4 book ai didi

c# - 为什么 enum.ToString box/callvirt,而不是推送地址和调用?还有其他特殊情况吗?

转载 作者:太空狗 更新时间:2023-10-30 00:19:16 24 4
gpt4 key购买 nike

我有几个月前编写的这个框架,它生成一个类来调用此性能服务。框架的消费者使用方法创建一个接口(interface),使用属性进行注释,并调用一个工厂方法来创建他们可以用来调用此性能服务的接口(interface)的实现。该服务只支持字符串和长两种数据。我将反射发射与可收集程序集结合使用来生成实现该接口(interface)的类。

一切都运行良好,但今天有人告诉我,当他们试图传入一个将被转换为字符串的枚举时,他们得到了一个 AV。在代码中,检查类型是否为值类型,如果是,则推送地址(ldarga 或 ldflda,具体取决于消费者创建的接口(interface)),然后调用 ToString。所以我创建了一个小调试应用程序,我看到 C# 编译器将封装一个枚举,然后在封装的枚举上调用 ToString。

所以我有点困惑。我处理值类型的方式不正确吗? C# 编译器为枚举上的 toString 生成的 IL 是正确的方法吗?还有其他像这样的特殊情况吗

更新答案:所以看起来我需要查看值类型是否实现了 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);
}
}
}

最佳答案

枚举类型不会覆盖其ToString() 方法,因此对于任何枚举类型ee.ToString() 解析到 Enum.ToString。此方法是在引用类型上定义的(Enum 是引用类型),因此要调用此方法,隐式 this 参数需要是一个装箱值。

大多数其他值类型,例如 int,确实直接在值类型本身上提供了一个重写的 ToString 方法。

来自规范:

I.12.1.6.2.4 Calling methods

Static methods on value types are handled no differently from static methods on an ordinary class: use a call instruction with a metadata token specifying the value type as the class of the method. Non-static methods (i.e., instance and virtual methods) are supported on value types, but they are given special treatment. A non-static method on a reference type (rather than a value type) expects a this pointer that is an instance of that class. This makes sense for reference types, since they have identity and the this pointer represents that identity. Value types, however, have identity only when boxed. To address this issue, the this pointer on a non-static method of a value type is a byref parameter of the value type rather than an ordinary by-value parameter.

A non-static method on a value type can be called in the following ways:

  • For unboxed instances of a value type, the exact type is known statically. The call instruction can be used to invoke the function, passing as the first parameter (the this pointer) the address of the instance. The metadata token used with the call instruction shall specify the value type itself as the class of the method.

  • Given a boxed instance of a value type, there are three cases to consider:

    • Instance or virtual methods introduced on the value type itself: unbox the instance and call the method directly using the value type as the class of the method.

    • Virtual methods inherited from a base class: use the callvirt instruction and specify the method on the System.Object, System.ValueType or System.Enum class as appropriate.

    • Virtual methods on interfaces implemented by the value type: use the callvirt instruction and specify the method on the interface type.

关于c# - 为什么 enum.ToString box/callvirt,而不是推送地址和调用?还有其他特殊情况吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22711443/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com