Comment puis-je obtenir une instance d'objet from () => foo.Title expression

.net-3.5 c# data-binding expression-trees lambda

Question

J'ai une classe simple avec une propriété

class Foo 
{ 
    string Title { get; set; } 
}

J'essaie de simplifier la liaison de données en appelant une fonction comme

BindToText(titleTextBox, ()=>foo.Title );

qui est déclaré comme

void BindToText<T>(Control control, Expression<Func<T>> property)
{
    var mex = property.Body as MemberExpression;
    string name = mex.Member.Name;

    control.DataBindings.Add("Text", ??? , name);
}

alors qu'est-ce que je mets ??? pour l'instance de ma classe Foo . Comment obtenir une référence à l'instance foo appelante à partir de l'expression lambda?

edit: l'instance devrait être présente quelque part car je peux appeler property.Compile() et créer un délégué qui utilise l'instance foo dans ma fonction BindToText . Ma question est donc de savoir si cela peut être fait sans ajouter de référence à l'instance dans les paramètres de la fonction. J'appelle le rasoir d' Occum pour obtenir la solution la plus simple.

edit 2: Ce que beaucoup n'ont pas remarqué, c'est la fermeture de l'accès à l'instance de foo dans ma fonction, si je compile le lambda. Comment se fait-il que le compilateur sache où trouver l'instance, et moi pas? J'insiste pour qu'il y ait une réponse, sans avoir à passer un argument supplémentaire.


Solution

Grâce à VirtualBlackFox, la solution est la suivante:

void BindText<T>(TextBoxBase text, Expression<Func<T>> property)
{
    var mex = property.Body as MemberExpression;
    string name = mex.Member.Name;
    var fex = mex.Expression as MemberExpression;
    var cex = fex.Expression as ConstantExpression;            
    var fld = fex.Member as FieldInfo;
    var x = fld.GetValue(cex.Value);
    text.DataBindings.Add("Text", x, name);            
}

ce qui me permet de taper simplement BindText(titleText, () => foo.Title); .

Réponse acceptée

Petit échantillon LINQPad de ce que vous voulez:

void Foo<T>(Expression<Func<T>> prop)
{
    var propertyGetExpression = prop.Body as MemberExpression;

    // Display the property you are accessing, here "Height"
    propertyGetExpression.Member.Name.Dump();

    // "s" is replaced by a field access on a compiler-generated class from the closure
    var fieldOnClosureExpression = propertyGetExpression.Expression as MemberExpression;

    // Find the compiler-generated class
    var closureClassExpression = fieldOnClosureExpression.Expression as ConstantExpression;
    var closureClassInstance = closureClassExpression.Value;

    // Find the field value, in this case it's a reference to the "s" variable
    var closureFieldInfo = fieldOnClosureExpression.Member as FieldInfo;
    var closureFieldValue = closureFieldInfo.GetValue(closureClassInstance);

    closureFieldValue.Dump();

    // We know that the Expression is a property access so we get the PropertyInfo instance
    // And even access the value (yes compiling the expression would have been simpler :D)
    var propertyInfo = propertyGetExpression.Member as PropertyInfo;
    var propertyValue = propertyInfo.GetValue(closureFieldValue, null);
    propertyValue.Dump();
}

void Main()
{
    string s = "Hello world";
    Foo(() => s.Length);
}

Réponse populaire

Ne pas Modifiez simplement la méthode pour prendre un autre paramètre, comme décrit dans # 3444294 . Pour votre exemple, cela peut ressembler à ceci:

void BindToText<T>(Control control, T dataSource, Expression<Func<T>> property)
{
    var mex = property.Body as MemberExpression;
    string name = mex.Member.Name;

    control.DataBindings.Add("Text", dataSource, name);
}

et serait appelé comme

BindToText(titleTextBox, foo, ()=>foo.Title );

Toujours sympa, mais facile à comprendre. Il n'y a pas de magie qui se passe. ;)



Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi