web-dev-qa-db-fra.com

Recherche du nom de variable transmis à une fonction

Permettez-moi d'utiliser l'exemple suivant pour expliquer ma question:

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

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

Lorsque je passe la variable "WhatIsMyName" à la fonction d'exemple, je veux pouvoir obtenir une chaîne du nom des variables d'origine. Peut-être quelque chose comme:

Variable.OriginalName.ToString()

Est-ce qu'il y a un moyen de faire ça?

56
GateKiller

Ce que vous voulez n'est pas possible directement mais vous pouvez utiliser des expressions en C # 3.0:

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

ExampleFunction(x => WhatIsMyName);

Notez que cela repose sur un comportement non spécifié et bien qu'il fonctionne dans les compilateurs C # et VB compilateurs, et du compilateur C # de Mono, il n'y a aucune garantie que cela ne fonctionne pas cesser de fonctionner dans les futures versions.

57
Konrad Rudolph

Ce n'est pas exactement possible, comme vous le souhaitez. C # 6.0, ils présentent le nom de l'opérateur qui devrait aider à améliorer et à simplifier le code. Le nom de l'opérateur résout le nom de la variable qui lui est passée.

L'utilisation de votre cas ressemblerait à ceci:

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

Un avantage majeur est que cela se fait au moment de la compilation,

Le nom de l'expression est une constante. Dans tous les cas, nameof (...) est évalué au moment de la compilation pour produire une chaîne. Son argument n'est pas évalué au moment de l'exécution et est considéré comme du code inaccessible (cependant, il n'émet pas d'avertissement "code inaccessible").

Plus d'informations peuvent être trouvées ici

Ancienne version de C 3.0 et supérieur
Pour construire sur la réponse de 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;
}
31
johnny 5
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;
}

Plus de détails sont dans ce billet de blog .

18
Rinat Abdullin

Trois façons:

1) Quelque chose sans réflexion du tout:

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) Utilise la réflexion, mais c'est beaucoup plus rapide que les deux autres.

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) Le plus lent de tous, ne l'utilisez pas.

GetParameterName3(() => variable);

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

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

Pour obtenir un nom et une valeur de paramètre combiné, vous pouvez étendre ces méthodes. Bien sûr, il est facile d'obtenir de la valeur si vous passez le paramètre séparément comme autre argument, mais ce n'est pas élégant. Au lieu:

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 et 2 sont de vitesse comparable maintenant, 3 est à nouveau lent.

13
nawfal

Non, mais chaque fois que vous vous trouvez à faire des choses extrêmement complexes comme celle-ci, vous voudrez peut-être repenser votre solution. N'oubliez pas que le code devrait être plus facile à lire qu'à écrire.

4
Nate Kohari

Oui! C'est possible. Je cherchais une solution à cela depuis longtemps et j'ai finalement trouvé un hack qui le résout (c'est un peu méchant). Je ne recommanderais pas d'utiliser cela dans le cadre de votre programme et je pense seulement que cela fonctionne en mode débogage. Pour moi, cela n'a pas d'importance car je ne l'utilise que comme outil de débogage dans ma classe de console afin que je puisse faire:

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

la sortie vers la console serait:

testVar: 1
testBoolVar: True

Voici la fonction que j'utilise pour ce faire (sans inclure le code d'habillage pour ma classe de console.

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

System.Environment.StackTrace vous donnera une chaîne qui inclut la pile d'appels actuelle. Vous pouvez analyser cela pour obtenir les informations, qui incluent les noms de variables pour chaque appel.

2
kevin42

Eh bien, essayez cette classe utilitaire,

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


}

Et l'utiliser comme

    /*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;
    }
2
Dipon Roy

Faites ceci

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

ou nommer le code à la main

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

en utilisant cette classe

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

Testé par le code et le plus élégant que je puisse imaginer.

1
kernowcode

GateKiller, quel est le problème avec ma solution? Vous pouvez réécrire votre fonction trivialement pour l'utiliser (j'ai pris la liberté d'améliorer la fonction à la volée):

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

Appelez ça comme ceci:

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

Merci pour toutes les réponses. Je suppose que je vais devoir continuer avec ce que je fais maintenant.

Pour ceux qui voulaient savoir pourquoi j'ai posé la question ci-dessus. J'ai la fonction suivante:

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

Je lui envoie un tableau de messages d'erreur et une classe css qui est ensuite renvoyée sous forme de chaîne pour une page Web.

Chaque fois que j'appelle cette fonction, je dois définir sType. Quelque chose comme:

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

Comme vous pouvez le voir, mes variables s'appellent aErrors et ma classe css s'appelle errors. J'espérais que mon rhume pourrait comprendre quelle classe utiliser en fonction du nom de variable que je lui ai envoyé.

Encore une fois, merci pour toutes les réponses.

0
GateKiller

Non. Une référence à votre variable chaîne est transmise à la fonction - il n'y a aucune métadeta inhérente à ce sujet incluse. Même la réflexion ne vous ferait pas sortir du bois ici - travailler en arrière à partir d'un seul type de référence ne vous donne pas assez d'informations pour faire ce que vous devez faire.

Mieux vaut revenir à la planche à dessin sur celui-ci!

rp

0
rp.

Vous pouvez utiliser la réflexion pour obtenir toutes les propriétés d'un objet, puis parcourir ce dernier et obtenir la valeur de la propriété où le nom (de la propriété) correspond au paramètre passé.

0
Adam Vigh