UFO ET IT

함수에 전달 된 변수 이름 찾기

ufoet 2020. 12. 11. 19:02
반응형

함수에 전달 된 변수 이름 찾기


내 질문을 설명하기 위해 다음 예제를 사용하겠습니다.

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

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

예제 함수에 "WhatIsMyName"변수를 전달할 때 원래 변수 이름의 문자열을 가져올 수 있기를 원합니다. 아마도 다음과 같습니다.

Variable.OriginalName.ToString()

이것을 할 방법이 있습니까?


** 아니요 ** 그렇게 생각하지 않습니다.

사용하는 변수 이름은 사용자의 편의와 가독성을위한 것입니다. 컴파일러는 그것을 필요로하지 않으며 내가 착각하지 않으면 그냥 버립니다.

도움이된다면 Name 및 Param 속성을 사용하여 NamedParameter라는 새 클래스를 정의 할 수 있습니다. 그런 다음이 개체를 매개 변수로 전달합니다.


원하는 것은 직접 불가능하지만 C # 3.0에서 식을 사용할 수 있습니다.

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

ExampleFunction(x => WhatIsMyName);

이것은 지정되지 않은 동작에 의존하며 Microsoft의 현재 C # 및 VB 컴파일러 Mono의 C # 컴파일러에서 작동하지만 향후 버전에서 작동이 중단되지 않을 것이라는 보장은 없습니다.


이것은 당신이 원하는 방식으로 정확히 가능하지 않습니다. C # 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 표현식은 상수입니다. 모든 경우에, nameof (...)는 컴파일 타임에 평가되어 문자열을 생성합니다. 인수는 런타임에 평가되지 않으며 도달 할 수없는 코드로 간주됩니다 (그러나 "도달 할 수없는 코드"경고를 표시하지 않음).

자세한 정보는 여기 에서 찾을 수 있습니다 .


Nawfals 답변을 구축하려면 C 3.0 이상의 이전 버전

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;
}

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;
}

자세한 내용은 이 블로그 게시물에 있습니다.


세 가지 방법 :

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;
}

콤보 매개 변수 이름과 값을 얻으려면 이러한 메서드를 확장 할 수 있습니다. 물론 매개 변수를 다른 인수로 개별적으로 전달하면 값을 쉽게 얻을 수 있지만 이는 우아하지 않습니다. 대신 :

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);
}

삼)

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은 다시 느립니다.


아니요,하지만 이와 같이 매우 복잡한 일을하고있는 자신을 발견 할 때마다 솔루션을 다시 생각하고 싶을 수 있습니다. 코드는 작성하는 것보다 읽기가 더 쉬워야합니다.


예! 것이 가능하다. 나는 이것에 대한 해결책을 오랫동안 찾고 있었고 마침내 그것을 해결하는 해킹을 찾았습니다 (조금 불쾌합니다). 나는 이것을 프로그램의 일부로 사용하지 않는 것이 좋으며 디버그 모드에서만 작동한다고 생각합니다. 나에게는 콘솔 클래스에서 디버깅 도구로만 사용하므로 문제가되지 않습니다.

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;
        }
    }

System.Environment.StackTrace는 현재 호출 스택을 포함하는 문자열을 제공합니다. 이를 구문 분석하여 각 호출에 대한 변수 이름을 포함하는 정보를 얻을 수 있습니다.


이 유틸리티 클래스를 사용해보십시오.

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;
    }

이 작업을 수행

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");
    }        
}

코드 테스트를 거쳐 내가 생각 해낼 수있는 가장 우아합니다.


No. A reference to your string variable gets passed to the funcion--there isn't any inherent metadeta about it included. Even reflection wouldn't get you out of the woods here--working backwards from a single reference type doesn't get you enough info to do what you need to do.

Better go back to the drawing board on this one!

rp


You could use reflection to get all the properties of an object, than loop through it, and get the value of the property where the name (of the property) matches the passed in parameter.


Thanks for all the responses. I guess I'll just have to go with what I'm doing now.

For those who wanted to know why I asked the above question. I have the following function:

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;
}

I send it an array of error messages and a css class which is then returned as a string for a webpage.

Every time I call this function, I have to define sType. Something like:

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

As you can see, my variables is called aErrors and my css class is called errors. I was hoping my cold could figure out what class to use based on the variable name I sent it.

Again, thanks for all the responses.


GateKiller, what's wrong with my workaround? You could rewrite your function trivially to use it (I've taken the liberty to improve the function on the fly):

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();
}

Call it like this:

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

This would be very useful to do in order to create good exception messages causing people to be able to pinpoint errors better. Line numbers help, but you might not get them in prod, and when you do get them, if there are big statements in code, you typically only get the first line of the whole statement.

For instance, if you call .Value on a nullable that isn't set, you'll get an exception with a failure message, but as this functionality is lacking, you won't see what property was null. If you do this twice in one statement, for instance to set parameters to some method, you won't be able to see what nullable was not set.

Creating code like Verify.NotNull(myvar, nameof(myvar)) is the best workaround I've found so far, but would be great to get rid of the need to add the extra parameter.


The short answer is no ... unless you are really really motivated.

The only way to do this would be via reflection and stack walking. You would have to get a stack frame, work out whereabouts in the calling function you where invoked from and then using the CodeDOM try to find the right part of the tree to see what the expression was.

For example, what if the invocation was ExampleFunction("a" + "b")?


Well had a bit of look. of course you can't use any Type information. Also, the name of a local variable is not available at runtime because their names are not compiled into the assembly's metadata.

참고URL : https://stackoverflow.com/questions/72121/finding-the-variable-name-passed-to-a-function

반응형