Getting the calling variable name of a parameter
Asked Answered
G

2

8

In relation to the question Get the name of parameters from a calling method and Finding the Variable Name passed to a Function in C# I'm still looking for a way to define theWhatDoesTheAnimalSay_WANTED method: I want to know the name of the variable that's used as a parameter:

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

In real life I need this while implementing the IXmlSerializable interface in my classes with custom reader.Read... and writer.Write.... methods.

So, unfortunately, I cannot introduce a new wrapper class, interface or change where the name of the animal or what it says is saved. Meaning it has to work with classes and with a plain string, int, decimal, ... variables, fields or properties as well. In other words (not in a rude way): Don't change how are the animals defined, just change the WhatDoesTheAnimalSay_WANTED method...

EDIT:

As some of you wanted to know a real use case for this example I'm giving you here an idea how I store & read data in an xml-file. The real Database object is of course way bigger and all sub-classes (like Fitter) implement the IXmlSerializable interface as well using the same extensions methods.

    // 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....
    }
}
Glorianna answered 5/6, 2016 at 7:5 Comment(5)
Your question isn't clear. You are trying to create a class based on something that isn't constant. You aren't going to be able to create a specific class with something that has a public setter for Says.Bailiwick
@Bailiwick Thanks for your input. I just altered my question. The class is there to show it should work with it as well. I hope its clearer now...Glorianna
That information is not available. Method parameters get pushed on the stack before a call, without metadata. It could be an interned string, a static member, an instance member or a local variable that is passed; you won't know. If you want the information, you'll have to pass it from the call site.Thallus
Please provide those use cases and ways you are calling it in your question. I am heading to bed if someone else hasn't looked I'll check again in the morning.Bailiwick
@Thallus HHHmmmm... OK. I was hoping there is perhaps something similar as the [CallerMemberName] attribute which can do that. Something like [CallerParameterName]....Glorianna
E
14

You can use a method which is taking an Expression<Func<object>> as parameter:

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

Using this approach gives the ability to process a variety of variables (static members, instance members, parameters, local variables etc.), also properties are possible.

Call it like:

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

Constants are not possible, because the compiler will substitute the constant placeholder by its value, given at compile time:

const string constValue = "Constant Value";

WhatDoesTheAnimalSay_WANTED(() => constValue)

would be transformed to

WhatDoesTheAnimalSay_WANTED(() => "Constant Value")

making the expression.Body of type ConstantExpression, which would yield an exception at runtime.

So you have to be careful what you provide as expression to that method.

Additional Remarks

As you can notice from the comments below, the use of lambda expressions to gather variable names seems controversial.

As @CodeCaster pointed out in one of his comments, there is no officially specified need for the compiler to take the same name of a local variable for the captured member in the anonymous wrapping class.

However, I found this in the remarks of Expression<TDelegate>:

The ability to treat expressions as data structures enables APIs to receive user code in a format that can be inspected, transformed, and processed in a custom manner.

For me this is a sign that the expression trees are exactly designed for purposes like that.

Although it is possible that Microsoft changes this behavior for some reason, there does not seem to be a logical need for doing so. Relying on the principle of least astonishment I'd say it is safe to assume that for an expression from ()=> dog whose Body property is of type MemberExpression, that body.Member.Name resolves to dog.

If it is necessary to have other types for Body also, the method has to be worked out a little bit more. And also it is possible that this will not work in certain circumstances anyways.

Emersed answered 5/6, 2016 at 8:58 Comment(10)
I guess you got the idea from the question the OP links to (Finding the Variable Name passed to a Function in C#), so please also mention the caveats mentioned there: "Note that this relies on unspecified behaviour". There is no guarantee that the body.Member.Name actually represents the variable name being used. This will also throw when not called with an existing variable but a constant or function call (() => "foo", () => dog.ToLower()).Thallus
See also Getting names of local variables (and parameters) at run-time through lambda expressions and Reflecting parameter name: abuse of C# lambda expressions or Syntax brilliance?. It is a "coincidence" that the body.Member.Name represents the local variable name (dog) used in () => dog.Thallus
No, I use this in a way more coplex way in my source codes. Mostly if I need refactor save property names in .NET 3.5 for INotifyPropertyChanged implementations. But I found out in evolving my libraries, that there is much more you can do with those lambda expressions. By the way, what else should body.Member.Name contain if body represents a MemberExpression?Emersed
Look; dog is not a member, it's a local variable. Because of its usage in a lambda, it gets compiled into an class and stored as a member of that class. It is not specified that this class's member has to have the same name as the variable used to construct the lambda; it could as well be arg1 or a<__>``1 or whatever the compiler thought to be useful. This is explained in the two questions I link to in my comment above.Thallus
Well, exactly this is how Linq works, using anonymous classes and using the members named after the inserted variable. Just compare something like var result = someCollection.Select(i => new { i.Property, localVar });. If you now use result inside a foreach loop, this gives you the possibility to access the values in the elements directly by elem.Property or also elem.localVar. This is especialy neccessary if you put the elements in whole as dynamic parameter to another method. So I suspect this is by design. That it was not thought about other uses at first, stays on the other hand.Emersed
That's where you type an anonymous type in code; that is specified. How a lambda expression gets compiled is not publicly specified and thus subject to change.Thallus
This is not how I understand this, look at the remarks of Expression<TDelegate> Class: The ability to treat expressions as data structures enables APIs to receive user code in a format that can be inspected, transformed, and processed in a custom manner. So again: what is body.Member.Name expected to contain if body is a MemberExpression even when it is from a statement like () => dog? Don't suspect things more complicated as neccessary.Emersed
I don't feel like repeating again what's explained in the two Q&As I linked to, try reading through them.Thallus
@Emersed I really like your answer as it solves the problem very nicely! On the other hand I'm with CodeCaster as well as M$ can change this behaviour when they feel like it. But then again, why should they do that!? Would you mind to add the objection of CodeCaster as a hint to your post? If its in their I have no problem to mark your post as the right answer. Thanks.Glorianna
@Soko: If you want a similar pattern to this but without the reliance on undefined behaviour you could use anonymous types with projection initialization instead of expression trees. For example, you'd call WhatDoesTheAnimalSay_WANTED(new { dog }), WhatDoesTheAnimalSay_WANTED(new { Cat }) etc, and the method would grab the projected property name and value out of the passed anon type. It's ugly and cumbersome, but the behaviour is fully guaranteed by the language spec.Yeorgi
C
3

I'm afraid there currently is no way to achieve what you want. The local variable name is not available to the called function.

There are alternatives, you listed some. Another alternative would be to put the name of the variable as a property inside the class. That would be the better design anyway because data in your application should not depend on variable names. Your application should work the same way if someone renamed all the variables.

Other languages like the C/C++ family uses preprocessor macros for that purpose but they are not available in C#. You could probably achieve this in C++/CLI, but only inside without the option to export such a macro outside your compilation unit and/or assembly.

So No, sorry, even with C# 6 and it's new features, this is not possible. And it should not need to be. Think about your design, you might come up with a better one.

Cuspidation answered 5/6, 2016 at 8:31 Comment(7)
"even with C# 6" - I'm fairly sure that you could write custom code, for example the attribute suggested by the OP and/or a naming convention (void Foo(string bar, int baz, [CallerParameterName] string nameOfbar = null, [CallerParameterName] string nameOfbaz = null)) and implement that by looking up and storing the parameter name at compile-time. It doesn't sound trivial to me though, nor reusable.Thallus
@Thallus As attributes don't do anything on their own without code using them as metadata, you would need to have compiler support for that.Cuspidation
I know, but with Roslyn you can hook into pretty much any phase of the compilation process.Thallus
@Cuspidation As you asked about the design and it should not rely on variable names I added my use case on the opening post. Do you think this is a bad design? If yes: Where else should I store the name of the XMLElement of the lists I'm writing? Happy for any input!Glorianna
@Glorianna I would store it in a static string constant. Because now, if someone decides to rename a variable, lets say for a spelling mistake or different naming scheme, your file format breaks. If you are using it just as a POCO, that's fine, but if that's a real class, that's bad.Cuspidation
@Cuspidation Understood. So basically you would leave my WriteElement...(...) extension methods as they are but instead of passing nameof(Fitters) as elementName you would use a static string constant defined in the Database class. Correct?Glorianna
@Glorianna Yes, exactly.Cuspidation

© 2022 - 2024 — McMap. All rights reserved.