Получение имени вызывающей переменной параметра

В отношении вопроса Получить имя параметра из вызывающего метода и Поиск имени переменной, переданного функции в С# Я все еще ищу способ определения метода WhatDoesTheAnimalSay_WANTED: я хочу знать имя переменной, которая используется в качестве параметра:

public class Farm
{
    public readonly string Cow = "Muuuuhh";

    public string Cat { get; set; }

    public void MainFunction()
    {
        var dog = "WauWau";
        var kiwi = new Bird("QueeeekQueeek");

        Cat = "Miiiaaauuuu";

        // This one works but looks kinda ugly and is cumbersome when used
        WhatDoesTheAnimalSay(nameof(dog), dog);
        WhatDoesTheAnimalSay(nameof(Cow), Cow);
        WhatDoesTheAnimalSay(nameof(Cat), Cat);
        WhatDoesTheAnimalSay(nameof(kiwi), kiwi);

        WhatDoesTheAnimalSay_WRONG1(dog);
        WhatDoesTheAnimalSay_WRONG2(dog);

        WhatDoesTheAnimalSay_WANTED(dog);
        WhatDoesTheAnimalSay_WANTED(Cow);
    }

    public void WhatDoesTheAnimalSay<T>(string name, T says)
    {
        MessageBox.Show("The " + name + " says: " + says);
        // Shows i.e.: The dog says: WauWau
    }

    public void WhatDoesTheAnimalSay_WRONG1<T>(T says)
    {
        MessageBox.Show("The " + nameof(says) + " says: " + says);
        // Shows: The says says: WauWau
    }

    public void WhatDoesTheAnimalSay_WRONG2<T>(T says, [CallerMemberName] string name = null)
    {
        MessageBox.Show("The " + name + " says: " + says);
        // Shows: The MainFunction says: WauWau
    }
    public void WhatDoesTheAnimalSay_WANTED<T>(T says /*, ?????*/)
    {
        MessageBox.Show("The " /*+ ?????*/ + " says: " + says);
        // Shows: The dog says: WauWau
    }
}

// Just here to show it should work with a class as well.
public class Bird
{
    public string Says { get; } //readonly
    public Bird(string says) {
        Says = says;
    }
    public override string ToString()
    {
        return Says;
    }
}

В реальной жизни мне это нужно, реализуя интерфейс IXmlSerializable в моих классах с помощью специальных методов reader.Read... и writer.Write.....

Итак, к сожалению, я не может представить новый класс оболочки, интерфейс или изменить, где имя животного или то, что он говорит, сохраняется. Это означает, что он должен работать с классами и с простой строкой, int, десятичной,... переменными, полями или свойствами. Другими словами (не грубо): не изменяйте, как определяются животные, просто измените метод WhatDoesTheAnimalSay_WANTED...

EDIT:

Поскольку некоторые из вас хотели узнать реальный пример использования для этого примера, я даю вам представление о том, как я храню и читаю данные в XML файле. Реальный объект базы данных, конечно, намного больше, и все подклассы (например, Fitter) реализуют интерфейс IXmlSerializable, используя те же методы расширения.

    // This is the Database Class which stores all the data
    public class Database : IXmlSerializable
    {
        // The list of all building sites managed
        public List<BuildingSite> BuildingSites { get; set; }

        // The list of all fitters working for the company
        public List<Fitter> Fitters { get; set; }

        private readonly int DatabaseVersion = 1;

        // Write implementation of the IXmlSerializable inteface
        public void WriteXml(XmlWriter writer)
        {
            // Writing all Data into the xml-file
            writer.WriteElementInt(nameof(DatabaseVersion), DatabaseVersion);
            writer.WriteElementList(nameof(BuildingSites), BuildingSites);
            writer.WriteElementList(nameof(Fitters), Fitters);
        }
        public void ReadXml(XmlReader reader)
        {
            // Do the reading here
        }
        public XmlSchema GetSchema() { return null; }

    }

    public class XmlExtensions
    {
        // Writing a list into the xml-file
        public static void WriteElementList<T>(this XmlWriter writer, string elementName, IEnumerable<T> items)
        {
            var list = items is List<T> ? items : items.ToList();

            // The XML-Element should have the name of the variable in Database!!!
            writer.WriteStartElement(elementName);
            writer.WriteAttributeString("count", list.Count().ToString());
            var serializer = new XmlSerializer(typeof(T));
            list.ForEach(o => serializer.Serialize(writer, o, XmlHelper.XmlNamespaces));
            writer.WriteEndElement();
        }
        public static void WriteElementInt(this XmlWriter writer, string elementName, int toWrite)
        {
            // The XMLElement should have the name of the variable in Database!!!
            writer.WriteElementString(elementName, toWrite.ToString(CultureInfo.InvariantCulture));
        }

        // More here....
    }
}

Ответ 1

Вы можете использовать метод, который принимает параметр Expression<Func<object>> as:

public void WhatDoesTheAnimalSay_WANTED(Expression<Func<object>> expression)
{
    var body = (MemberExpression)expression.Body;
    var variableName = body.Member.Name;

    var func = expression.Compile();
    var variableValue = func();

    MessageBox.Show("The "+ variableName + " says: " + variableValue);
}

Использование этого подхода дает возможность обрабатывать множество переменных (статические члены, члены экземпляра, параметры, локальные переменные и т.д.), а также свойства возможны.

Назовите его так:

WhatDoesTheAnimalSay_WANTED(() => dog)
WhatDoesTheAnimalSay_WANTED(() => Cow)
WhatDoesTheAnimalSay_WANTED(() => Cat)
WhatDoesTheAnimalSay_WANTED(() => kiwi)

Константы не возможны, поскольку компилятор заменит константу-заполнитель на это значение, указанное во время компиляции:

const string constValue = "Constant Value";

WhatDoesTheAnimalSay_WANTED(() => constValue)

будет преобразовано в

WhatDoesTheAnimalSay_WANTED(() => "Constant Value")

делает expression.Body типа ConstantExpression, что даст исключение во время выполнения.

Итак, вы должны быть осторожны с тем, что вы предоставляете в качестве выражения для этого метода.

Дополнительные замечания

Как вы можете заметить из комментариев ниже, использование лямбда-выражений для сбора имен переменных кажется противоречивым.

Как отметил @CodeCaster в одном из своих комментариев, официально не указано, что компилятор должен использовать одно и то же имя локальной переменной для захваченного элемента в анонимном классе упаковки.

Однако я нашел это в примечаниях Expression <TDelegate> :

Возможность обработки выражений в виде структур данных позволяет API-интерфейсам получать код пользователя в формате, который может быть проверен, преобразован и обработан по-своему.

Для меня это знак того, что деревья выражений точно предназначены для таких целей.

Несмотря на то, что Microsoft по какой-то причине может изменить это поведение, по-видимому, нет логической необходимости. Опираясь на принцип наименьшего удивления Я бы сказал, что можно с уверенностью предположить, что для выражения из ()=> dog, чье свойство Body типа MemberExpression, что body.Member.Name разрешает собаку.

Если необходимо также иметь другие типы для Body, метод должен быть разработан немного больше. И также, возможно, что это не будет работать в определенных обстоятельствах.

Ответ 2

Я боюсь, что в настоящее время нет способа добиться того, чего вы хотите. Локальное имя переменной недоступно вызываемой функции.

Есть альтернативы, вы указали некоторые. Другой альтернативой было бы присвоить имя переменной как свойство внутри класса. Это будет лучший дизайн, так как данные в вашем приложении не должны зависеть от имен переменных. Ваше приложение должно работать одинаково, если кто-то переименовал все переменные.

Другие языки, такие как семейство C/С++, используют макросы препроцессора для этой цели, но они недоступны на С#. Вероятно, вы могли бы достичь этого в С++/CLI, но только внутри, не имея возможности экспортировать такой макрос вне вашего модуля компиляции и/или сборки.

Итак, Нет, извините, даже с С# 6 и новыми функциями это невозможно. И это не должно быть. Подумайте о своем дизайне, вы можете придумать лучший вариант.