Как автоматически присвоить полю его же имя?

108
29 октября 2019, 06:00
public static class Class1
{
    public static string name1 = "name1";
    public static string name2 = this.Name;
}

К name2 автоматически присвоить значение его же имени, без указания имени в строке.

А можно как-то покороче чем это, без указания второй раз имени name2?:

public static string name2 = nameof(name2);
Answer 1

Решение для .NET 4.5+ / .NET Core

using System.Runtime.CompilerServices;
public static class MyName
{
     public static string Get([CallerMemberName] string s = "") { return s; }
}
public class Class1
{     
     public static string name1 = "name1";            
     public static string name2 = MyName.Get();            
}

Примечание. Тот факт, что при использовании в инициализаторе поля CallerMemberName возвращает имя этого поля (а не имя конструктора, который на самом деле является caller'ом), не документирован. Но это работает.

Решение для более старых версий .NET

using System;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;
public static class MyName
{
    static OpCode FindOpCode(short val)
    {
        OpCode ret = OpCodes.Nop;
        FieldInfo[] mas = typeof(OpCodes).GetFields();
        for (int i = 0; i < mas.Length; i++)
        {
            if (mas[i].FieldType == typeof(OpCode))
            {
                OpCode opcode = (OpCode)mas[i].GetValue(null);
                if (opcode.Value == val)
                {
                    ret = opcode;
                    break;
                }
            }
        }
        return ret;
    }
    static string GetFieldNameFromOffset(MethodBase mi, int offset)
    {
        MethodBody mb = null;
        string result = "";
        //получаем тело метода                
        mb = mi.GetMethodBody();
        if (mb == null) throw new ApplicationException("Fatal error: GetMethodBody failed!");
        //получаем IL-код
        var msil = mb.GetILAsByteArray();
        //получаем модуль, в котором расположен метод
        var module = mi.Module;
        short op;
        int n = offset;
        //парсим IL-код...
        while (true)
        {
            if (n >= msil.Length) break;
            //получаем код операции
            if (msil[n] == 0xfe)
                op = (short)(msil[n + 1] | 0xfe00);
            else
                op = (short)(msil[n]);
            //найдем имя операции
            OpCode opcode = FindOpCode(op);
            string str = opcode.Name;
            int size = 0;
            //найдем размер операции
            switch (opcode.OperandType)
            {
                case OperandType.InlineBrTarget: size = 4; break;
                case OperandType.InlineField: size = 4; break;
                case OperandType.InlineMethod: size = 4; break;
                case OperandType.InlineSig: size = 4; break;
                case OperandType.InlineTok: size = 4; break;
                case OperandType.InlineType: size = 4; break;
                case OperandType.InlineI: size = 4; break;
                case OperandType.InlineI8: size = 8; break;
                case OperandType.InlineNone: size = 0; break;
                case OperandType.InlineR: size = 8; break;
                case OperandType.InlineString: size = 4; break;
                case OperandType.InlineSwitch: size = 4; break;
                case OperandType.InlineVar: size = 2; break;
                case OperandType.ShortInlineBrTarget: size = 1; break;
                case OperandType.ShortInlineI: size = 1; break;
                case OperandType.ShortInlineR: size = 4; break;
                case OperandType.ShortInlineVar: size = 1; break;
                default:
                    throw new Exception("Unknown operand type.");
            }
            size += opcode.Size;
            int token = 0;
            if (n > offset && (str == "stsfld" || str == "stfld"))
            {
                //найдем токен метаданных поля
                token = (((msil[n + 1] | (msil[n + 2] << 8)) |
                    (msil[n + 3] << 0x10)) | (msil[n + 4] << 0x18));
                //найдем поле в модуле по токену
                var fi = module.ResolveField(token);
                result = fi.Name;
                return result;
            }
            n += size; //пропускаем нужное число байтов
        }
        return result;
    }
    [MethodImpl(MethodImplOptions.NoInlining)]
    public static string Get()
    {               
        //найдем вызывающий метод
        var stack = new StackTrace(true);                
        var frame = stack.GetFrame(1);  
        var method = frame.GetMethod();
        //найдем имя поля по смещению в IL
        var name = GetFieldNameFromOffset(method, frame.GetILOffset());
        return name;
    }
}
public class Class1
{     
    public static string name1 = "name1";            
    public static string name2 = MyName.Get();            
}

Для инициализатора поля name2 в коде конструктора компилятор генерирует последовательность IL-инструкций call и stsfld. Метод StackFrame.GetILOffset позволяет нам получить смещение инструкции call в байтах относительно начала кода конструктора, а MethodInfo.GetMethodBody позволяет получить сам IL-код конструктора. Чтобы получить FieldInfo, достаточно найти инструкцию, следующую за call, вытащить токен метаданных поля и передать его в метод Module.ResolveField. Из FieldInfo уже легко получить имя поля.

Answer 2

Давай представим себе ситуацию:

Cat Tomas = new Cat();
Cat Daryl = Tomas;

в даном случае и Tomas и Daryl переменные ссылаются на один и тот же обьект.

Какое значение должно вернутся если мы обратимся к Tomas.Name и какое если мы обратимся к Daryl.Name? Как в теории язык должен определить от какого имени был сделан вызов метода который возвращает имя?

Мое предположение в даном случае что можно сделать инициализацию имени переменной обязательной. Вроде

Cat Tomas = new Cat("Tomas");

Это не решает проблемы, зато работает в части случаев. (в примере выше, конечно же, работать не будет).

...........

Чисто в теории можно что-то придумать с стектрейсом. Но не факт что это будет работать в релизном билде.

...........

Чисто в теории возможно что-то придумать с рефлексией. Но не факт что это будет работать всегда как нужно и не выдавать ложных результатов, как в примере выше.

Вообще я настоятельно не советую так делать вообще в принципе. Используй nameof() в местах где тебе это нужно. Это гарантированно не принесет проблем.

А так же, скорее всего, ты движешся в неправильном направлении и придумал неправильное решение своей задачи.

READ ALSO
Сохранение данных textbox и Image

Сохранение данных textbox и Image

У меня есть 3 textbox(c внесенными данными),1 картинка которая загружается пользователем,1 Listbox в котором нужно выбрать вариант ответаКак и куда...

126
NAUDIO конвертация wav byte[] в mp3 byte[]

NAUDIO конвертация wav byte[] в mp3 byte[]

Уже перепробовал куча вариантовМне по сети приходят байты WaveIn с голосом и передаются в эту функцию:

119
Code First - не создаеся БД

Code First - не создаеся БД

В конструкторе класса контекста есть метод

106