Why is the enum.ToString / callvirt field instead of pressing an address and a call? Are there any other special cases?

I have this structure that I wrote a couple of months ago that generates a class to call this performance service. Framework consumers create an interface with methods, annotate attributes, and call the factory method, which creates an implementation of an interface that they can use to call this performance service. The service only supports two lines of data and a long one. I use emit reflection with collectible assemblies to create a class that implements the interface.

Everything works well, but today someone told me that they get AV when they tried to pass an enumeration that would be converted to a string. There is a check in the code to find out if the type is a value type, and if so, click the address (ldarga or ldflda depending on the interface created by the consumer), and then call ToString. So I created a small debugging application, and I saw that the C # compiler would enable the enumeration, and then call ToString on the enum box.

Since I'm a little confused. Is there a way to incorrectly handle type values? Is the C # IL compiler generating the correct way for toString on an enum? Are there other special cases like

Update with the answer: so it seems like I need to see if the type implements the tostring value, and if it is not inserted. For value types, I assume this applies to object methods, 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);
        }
    }
}
+4
source share
2 answers

An enumeration type does not override its method ToString(), therefore, for any type of enumeration e, it is e.ToString()allowed Enum.ToString. This method is defined by a reference type ( Enumis a reference type), therefore, to invoke this method, the implicit argument thismust be a short value.

, int, ToString .

:

I.12.1.6.2.4

, : call , . (.. ) , . ( ) this, . , , this . . , this byref , .

:

  • unboxed . call , ( ) . , call .

  • , :

    • , : , .

    • , : callvirt System.Object, System.ValueType System.Enum, .

    • , : callvirt .

+5

:

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
}

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__

}

, Foo ToString(). , : . - struct override a 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 
}

, JIT CLI , , , : , , (), , , ( ).

+4

Source: https://habr.com/ru/post/1533895/


All Articles