How can one get a property name as a string in managed c++
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;
}
Lambda expressions in C# are syntactic sugar for delegates, so you will have to find the delegate equivalent of the lambda expression to be able to have the same functionality in C++/CLI.
To make your life a bit easier, you could explore CLinq (see the "Lambda expressions" section) that provides a C++/CLI wrapper for Linq
Note: Do not confuse the C++11 lambda expressions with the C# lambda expressions. The former is supported only for native code. It is possible to use the C++11 lambdas, but you need to do some extra work to provide delegates for them (this CodeProject article explores the subject)