Search code examples
visual-c++propertiesc++-climanaged-c++

How can one get a property name as a string in managed c++


I see a lot of entries for doing this in C#, but nothing for C++

I have a set of properties in some managed C++ code that is used to pass the data between the C# portion and c++ portion. In the C# side, the answer presented here works very well and I'd like to do something similar with C++. Copy of solution contained in Link:

string NameOf<T>(Expression<Func<T>> expr) {
    return ((MemberExpression) expr.Body).Member.Name;
}

var gmtList = new SelectList(repository.GetSystemTimeZones(),
    NameOf(() => tz.Id),
    NameOf(() => tz.DisplayName));

My problem is that I can't seem to get the correct syntax for the call, specifically, this section:

() => tz.DisplayName

I can't seem to find a resource online that goes over how I would do this in C++, so if anybody has any experience or links, I would really appreciate any help.


Solution

  • I've struggled with all those lambdas and cool System::Linq namespaces (including the CLinq library and other things) for a couple of hours and then... I just thought: why not use a purely static decision ?

    We just declare the

    /// Convert X to "X", the classical preprocessing trick
    #define GetPropName(TheClassName, ThePropertyName) #ThePropertyName
    

    and then we can do

    Console::WriteLine( GetPropName(TimeZone, Id) );
    

    to get the "Id" printed on the screen.

    Yeah... Now the interesting part. The type safety. I hear the comment storm coming about this solution ("NO ! This is not good, it does not check if the ThePropertyName is in the class !")

    OK. The solution: let us produce some meaningless code using a macro which uses the ThePropertyName in a dummy TheClassName's instance.

    /// This macro will produce the compilation error if ThePropertyName is not in the class named TheClassName
    #define CheckForPropertyExistence(TheClassName, ThePropertyName) \
    /* Create an array of Objects which will be converted to string and ignored*/ \
    (gcnew array<System::Object^> { (gcnew TheClassName())->ThePropertyName })->ToString()
    
    /// We get the property name using the "dinosaur strategy" - good old macro concatenated with the empty string which in turn is formed in CheckFor() macro
    #define GetPropertyName(TheClassName, ThePropertyName) \
    (gcnew System::String(#ThePropertyName)) + CheckForPropertyExistence(TheClassName, ThePropertyName)->Substring(0,0)
    

    Now we can give the complete sample:

    using namespace System;
    
    /// Sample class
    public ref class TheTimeZone
    {
    public:
        TheTimeZone()
        {
            _Id = 0;
            _DisplayName = "tmp";
        }
    
        property int Id
        {
        public:
            int get() {return _Id;}
            void set(int v) { _Id = v; }
        }
    
        property String^ DisplayName
        {
        public:
            String^ get() { return _DisplayName; }
            void set(String^ v) { _DisplayName = v; }
        }
    
    private:
        int _Id;
        String^ _DisplayName;
    };
    
    /// This macro will produce the error if ThePropertyName is not in the class named TheClassName
    #define CheckForPropertyExistence(TheClassName, ThePropertyName) \
    /* Create an array of Objects which will be converted to string and ignored*/ \
    (gcnew array<System::Object^> { (gcnew TheClassName())->ThePropertyName })->ToString()
    
    /// We get the property name using the "dinosaur strategy":
    /// good old macro concatenated with the empty string
    /// which in turn is formed in CheckFor() macro
    #define GetPropertyName(TheClassName, ThePropertyName) \
    (gcnew System::String(#ThePropertyName)) + \
    CheckForPropertyExistence(TheClassName, ThePropertyName)->Substring(0,0)
    
    /// To get properties from objects with no default constructor
    #define GetPropertyNameForObject(TheObject, ThePropertyName) \
    (gcnew System::String(#ThePropertyName)) + \
    (gcnew array<System::Object^> { (TheObject)-> ThePropertyName })->ToString()->Substring(0,0)
    
    /// Test for our macros
    int main(array<System::String ^> ^args)
    {
        /// Prints "Length"
        /// We cannot use default constructor here
        Console::WriteLine(GetPropertyNameForObject (gcnew System::String("test"), Length) );
    
        /// Prints "Id"
        Console::WriteLine(GetPropertyName (TheTimeZone, Id) );
    
    /// Uncomment and get the error
        //Console::WriteLine(GetPropertyName (TheTimeZone, Id23) );
    
        return 0;
    }