Ho lottato con tutti quei lambda e gli spazi dei nomi di sistema :: Linq (compresa la libreria CLinq e altre cose) per un paio d'ore e poi ... Ho solo pensato: perché non usare una decisione puramente statica?
Abbiamo appena dichiariamo la
/// Convert X to "X", the classical preprocessing trick
#define GetPropName(TheClassName, ThePropertyName) #ThePropertyName
e poi possiamo fare
Console::WriteLine(GetPropName(TimeZone, Id));
per ottenere il "Id" stampato sullo schermo.
Sì ... Ora la parte interessante. Il tipo di sicurezza. Ho sentito che la tempesta di commenti è arrivata su questa soluzione ("NO! Non va bene, non controlla se ThePropertyName è nella classe!")
OK. La soluzione: produciamo codice senza senso usando una macro che usa ThePropertyName in un'istanza fittizia di TheClassName.
/// 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)
Ora possiamo dare l'esempio completo:
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;
}
Abbastanza bello/hacky ... Non avresti potuto decorare CheckForPropertyExistence con un '1? NULL: yourcode', così in fase di esecuzione sono stati creati due oggetti in meno? – xanatos
@xanatos: Sì, sembra essere una buona idea. Sono passati tre anni, non ricordo il ragionamento esatto per creare un oggetto. Forse solo la "soluzione trovata!" l'eccitazione mi ha impedito di fare come tu proponi. –