Поиск имени переменной, переданной функции

Позвольте мне использовать следующий пример, чтобы объяснить мой вопрос:

public string ExampleFunction(string Variable) {
    return something;
}

string WhatIsMyName = "Hello World"';
string Hello = ExampleFunction(WhatIsMyName);

Когда я передаю переменную "WhatIsMyName" в функцию example, я хочу иметь возможность получить строку имени исходных переменных. Возможно, что-то вроде:

Variable.OriginalName.ToString()

Есть ли способ сделать это?

Ответы

Ответ 1

** Нет. ** Я так не думаю.

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

Если это помогает, вы можете определить новый класс с именем NamedParameter с атрибутами Name и Param. Затем вы передаете этот объект в качестве параметров.

Ответ 2

То, что вы хотите, невозможно напрямую, но вы можете использовать выражения в С# 3.0:

public void ExampleFunction(Expression<Func<string, string>> f) {
    Console.WriteLine((f.Body as MemberExpression).Member.Name);
}

ExampleFunction(x => WhatIsMyName);

Обратите внимание, что это зависит от неуказанного поведения и, хотя оно работает в текущих компиляторах С# и VB Microsoft, и в компиляторе Monos С#, не гарантирует, что это не перестанет работать в будущих версиях.

Ответ 3

Это не совсем возможно, так, как вы хотели бы. С# 6.0 они вводят имя оператора, которое должно помочь улучшить и упростить код. Имя оператора разрешает имя передаваемой в него переменной.

Использование для вашего случая будет выглядеть так:

public string ExampleFunction(string variableName) {
      //Construct your log statement using c# 6.0 string interpolation
       return $"Error occurred in {variableName}";
}

string WhatIsMyName = "Hello World"';
string Hello = ExampleFunction(nameof(WhatIsMyName));

Основным преимуществом является то, что это делается во время компиляции,

Имя выражения является константой. Во всех случаях nameof (...) вычисляется во время компиляции для получения строки. Его аргумент не оценивается во время выполнения и считается недоступным кодом (однако он не выдает предупреждение "недоступный код").

Более подробную информацию можно найти здесь

Старая версия C 3.0 и выше
Чтобы построить на Nawfals ответ

GetParameterName2(new { variable });

//Hack to assure compiler warning is generated specifying this method calling conventions
[Obsolete("Note you must use a single parametered AnonymousType When Calling this method")]
public static string GetParameterName<T>(T item) where T : class
{
    if (item == null)
        return string.Empty;

    return typeof(T).GetProperties()[0].Name;
}

Ответ 4

static void Main(string[] args)
{
  Console.WriteLine("Name is '{0}'", GetName(new {args}));
  Console.ReadLine();
}

static string GetName<T>(T item) where T : class
{
  var properties = typeof(T).GetProperties();
  Enforce.That(properties.Length == 1);
  return properties[0].Name;
}

Более подробная информация находится в этом сообщении в блоге.

Ответ 5

Три способа:

1) Что-то без отражения вообще:

GetParameterName1(new { variable });

public static string GetParameterName1<T>(T item) where T : class
{
    if (item == null)
        return string.Empty;

    return item.ToString().TrimStart('{').TrimEnd('}').Split('=')[0].Trim();
}

2) Использует отражение, но это намного быстрее, чем другие два.

GetParameterName2(new { variable });

public static string GetParameterName2<T>(T item) where T : class
{
    if (item == null)
        return string.Empty;

    return typeof(T).GetProperties()[0].Name;
}

3) Самый медленный из всех, не используйте.

GetParameterName3(() => variable);

public static string GetParameterName3<T>(Expression<Func<T>> expr)
{
    if (expr == null)
        return string.Empty;

    return ((MemberExpression)expr.Body).Member.Name;
}

Чтобы получить имя и значение параметра combo, вы можете расширить эти методы. Конечно, его легко получить значение, если вы передадите параметр отдельно в качестве другого аргумента, но это неэффективно. Вместо этого:

1)

public static string GetParameterInfo1<T>(T item) where T : class
{
    if (item == null)
        return string.Empty;

    var param = item.ToString().TrimStart('{').TrimEnd('}').Split('=');
    return "Parameter: '" + param[0].Trim() +
           "' = " + param[1].Trim();
}

2)

public static string GetParameterInfo2<T>(T item) where T : class
{
    if (item == null)
        return string.Empty;

    var param = typeof(T).GetProperties()[0];
    return "Parameter: '" + param.Name +
           "' = " + param.GetValue(item, null);
}

3)

public static string GetParameterInfo3<T>(Expression<Func<T>> expr)
{
    if (expr == null)
        return string.Empty;

    var param = (MemberExpression)expr.Body;
    return "Parameter: '" + param.Member.Name +
           "' = " + ((FieldInfo)param.Member).GetValue(((ConstantExpression)param.Expression).Value);
}

1 и 2 теперь имеют сопоставимую скорость, 3 снова вяло.

Ответ 6

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

Ответ 7

Да! Возможно. Я искал решение этого в течение долгого времени и, наконец, придумал хак, который его решает (это немного противно). Я бы не рекомендовал использовать это как часть вашей программы, и я думаю, что он работает в режиме отладки. Для меня это не имеет значения, поскольку я использую его только как инструмент для отладки в моем классе консоли, поэтому я могу сделать:

int testVar = 1;
bool testBoolVar = True;
myConsole.Writeline(testVar);
myConsole.Writeline(testBoolVar);

вывод на консоль:

testVar: 1
testBoolVar: True

Вот функция, которую я использую для этого (не включая код оболочки для моего класса консоли.

    public Dictionary<string, string> nameOfAlreadyAcessed = new Dictionary<string, string>();
    public string nameOf(object obj, int level = 1)
    {
        StackFrame stackFrame = new StackTrace(true).GetFrame(level);
        string fileName = stackFrame.GetFileName();
        int lineNumber = stackFrame.GetFileLineNumber();
        string uniqueId = fileName + lineNumber;
        if (nameOfAlreadyAcessed.ContainsKey(uniqueId))
            return nameOfAlreadyAcessed[uniqueId];
        else
        {
            System.IO.StreamReader file = new System.IO.StreamReader(fileName);
            for (int i = 0; i < lineNumber - 1; i++)
                file.ReadLine();
            string varName = file.ReadLine().Split(new char[] { '(', ')' })[1];
            nameOfAlreadyAcessed.Add(uniqueId, varName);
            return varName;
        }
    }

Ответ 8

System.Environment.StackTrace предоставит вам строку, которая включает текущий стек вызовов. Вы можете разобрать это, чтобы получить информацию, которая включает имена переменных для каждого вызова.

Ответ 9

Ну попробуйте этот класс Utility,

public static class Utility
{
    public static Tuple<string, TSource> GetNameAndValue<TSource>(Expression<Func<TSource>> sourceExpression)
    {
        Tuple<String, TSource> result = null;
        Type type = typeof (TSource);
        Func<MemberExpression, Tuple<String, TSource>> process = delegate(MemberExpression memberExpression)
                                                                    {
                                                                        ConstantExpression constantExpression = (ConstantExpression)memberExpression.Expression;
                                                                        var name = memberExpression.Member.Name;
                                                                        var value = ((FieldInfo)memberExpression.Member).GetValue(constantExpression.Value);
                                                                        return new Tuple<string, TSource>(name, (TSource) value);
                                                                    };

        Expression exception = sourceExpression.Body;
        if (exception is MemberExpression)
        {
            result = process((MemberExpression)sourceExpression.Body);
        }
        else if (exception is UnaryExpression)
        {
            UnaryExpression unaryExpression = (UnaryExpression)sourceExpression.Body;
            result = process((MemberExpression)unaryExpression.Operand);
        }
        else
        {
            throw new Exception("Expression type unknown.");
        }

        return result;
    }


}

И пользователю нравится

    /*ToDo : Test Result*/
    static void Main(string[] args)
    {
        /*Test : primivit types*/
        long maxNumber = 123123;
        Tuple<string, long> longVariable = Utility.GetNameAndValue(() => maxNumber);
        string longVariableName = longVariable.Item1;
        long longVariableValue = longVariable.Item2;

        /*Test : user define types*/
        Person aPerson = new Person() { Id = "123", Name = "Roy" };
        Tuple<string, Person> personVariable = Utility.GetNameAndValue(() => aPerson);
        string personVariableName = personVariable.Item1;
        Person personVariableValue = personVariable.Item2;

        /*Test : anonymous types*/
        var ann = new { Id = "123", Name = "Roy" };
        var annVariable = Utility.GetNameAndValue(() => ann);
        string annVariableName = annVariable.Item1;
        var annVariableValue = annVariable.Item2;

        /*Test : Enum tyoes*/
        Active isActive = Active.Yes;
        Tuple<string, Active> isActiveVariable = Utility.GetNameAndValue(() => isActive);
        string isActiveVariableName = isActiveVariable.Item1;
        Active isActiveVariableValue = isActiveVariable.Item2;
    }

Ответ 10

Нет. Ссылка на вашу строковую переменную передается в funcion - в нее нет встроенной метадаеты. Даже отражение не вывело бы вас из леса здесь - работа назад от одного ссылочного типа не дает вам достаточно информации, чтобы делать то, что вам нужно.

Лучше вернуться к чертежной доске на этом!

гр

Ответ 11

Вы можете использовать отражение, чтобы получить все свойства объекта, а не цитировать его, и получить значение свойства, в котором имя (свойства) соответствует переданному параметру.

Ответ 12

Спасибо за все ответы. Думаю, мне просто нужно пойти с тем, что я сейчас делаю.

Для тех, кто хотел знать, почему я задал вышеуказанный вопрос. У меня есть следующая функция:

string sMessages(ArrayList aMessages, String sType) {
    string sReturn = String.Empty;
    if (aMessages.Count > 0) {
        sReturn += "<p class=\"" + sType + "\">";
        for (int i = 0; i < aMessages.Count; i++) {
            sReturn += aMessages[i] + "<br />";
        }
        sReturn += "</p>";
    }
    return sReturn;
}

Я отправляю ему массив сообщений об ошибках и класс css, который затем возвращается как строка для веб-страницы.

Каждый раз, когда я вызываю эту функцию, я должен определить sType. Что-то вроде:

output += sMessages(aErrors, "errors");

Как вы можете видеть, мои переменные называются aErrors, а мой класс css называется ошибками. Я надеялся, что мой холод может понять, какой класс использовать на основе имени переменной, которое я ему отправил.

Снова, спасибо за все ответы.

Ответ 13

GateKiller, что случилось с моим обходным решением? Вы можете переписать свою функцию тривиально, чтобы использовать ее (я взял на себя смелость улучшить функцию "на лету" ):

static string sMessages(Expression<Func<List<string>>> aMessages) {
    var messages = aMessages.Compile()();

    if (messages.Count == 0) {
        return "";
    }

    StringBuilder ret = new StringBuilder();
    string sType = ((MemberExpression)aMessages.Body).Member.Name;

    ret.AppendFormat("<p class=\"{0}\">", sType);
    foreach (string msg in messages) {
        ret.Append(msg);
        ret.Append("<br />");
    }
    ret.Append("</p>");
    return ret.ToString();
}

Назовите его следующим образом:

var errors = new List<string>() { "Hi", "foo" };
var ret = sMessages(() => errors);

Ответ 14

Сделайте это

var myVariable = 123;
myVariable.Named(() => myVariable);
var name = myVariable.Name();
// use name how you like

или именование в коде вручную

var myVariable = 123.Named("my variable");
var name = myVariable.Name();

используя этот класс

public static class ObjectInstanceExtensions
{
    private static Dictionary<object, string> namedInstances = new Dictionary<object, string>();

    public static void Named<T>(this T instance, Expression<Func<T>> expressionContainingOnlyYourInstance)
    {
        var name = ((MemberExpression)expressionContainingOnlyYourInstance.Body).Member.Name;
        instance.Named(name);            
    }

    public static T Named<T>(this T instance, string named)
    {
        if (namedInstances.ContainsKey(instance)) namedInstances[instance] = named;
        else namedInstances.Add(instance, named);
        return instance;
    }        

    public static string Name<T>(this T instance)
    {
        if (namedInstances.ContainsKey(instance)) return namedInstances[instance];
        throw new NotImplementedException("object has not been named");
    }        
}

Проверенный код и самый элегантный я могу придумать.

Ответ 15

Короткий ответ - нет... если вы действительно не мотивированы.

Единственный способ сделать это - через размышление и стекирование. Вам нужно будет получить фрейм стека, определить местонахождение в вызывающей функции, из которой вы вызывается, а затем с помощью CodeDOM попытаться найти нужную часть дерева, чтобы увидеть, что это за выражение.

Например, что если вызов был ExampleFunction ( "a" + "b" )?

Ответ 16

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