2011-01-04 8 views
14

Me gustaría poder generar una expresión compilada para establecer una propiedad, dada la expresión lambda que proporciona el método "obtener" para una propiedad.Crear una acción <T> para "establecer" una propiedad, cuando se me proporcione la expresión LINQ para "obtener"

Aquí es lo que estoy buscando:

public Action<int> CreateSetter<T>(Expression<Func<T, int>> getter) 
{ 
    // returns a compiled action using the details of the getter expression tree, or null 
    // if the write property is not defined. 
} 

Todavía estoy tratando de entender los distintos tipos de clases de expresión, por lo que si usted me puede apuntar en la dirección correcta, que sería grande.

+1

¿No te refieres a 'Action '? Además, podríamos hacer 'int' un parámetro de tipo también. – Ani

+0

Ani, sí, tienes razón, me refería a la Acción y, por supuesto, int también será un parámetro genérico. – Alex

Respuesta

12

Usando @ respuesta de Ani como punto de partida, puede utilizar el siguiente para generar una expresión compilado.

[TestMethod] 
public void CreateSetterFromGetter() 
{ 
    Action<Person, int> ageSetter = InitializeSet((Person p) => p.Age); 
    Action<Person, string> nameSetter = InitializeSet((Person p) => p.Name); 

    Person p1 = new Person(); 
    ageSetter(p1, 29); 
    nameSetter(p1, "John"); 

    Assert.IsTrue(p1.Name == "John"); 
    Assert.IsTrue(p1.Age == 29); 
} 

public class Person { public int Age { get; set; } public string Name { get; set; } } 

public static Action<TContainer, TProperty> InitializeSet<TContainer, TProperty>(Expression<Func<TContainer, TProperty>> getter) 
{ 
    PropertyInfo propertyInfo = (getter.Body as MemberExpression).Member as PropertyInfo; 

    ParameterExpression instance = Expression.Parameter(typeof(TContainer), "instance"); 
    ParameterExpression parameter = Expression.Parameter(typeof(TProperty), "param"); 

    return Expression.Lambda<Action<TContainer, TProperty>>(
     Expression.Call(instance, propertyInfo.GetSetMethod(), parameter), 
     new ParameterExpression[] { instance, parameter }).Compile(); 
} 

Debe almacenar en caché la expresión compilada para tenerla a mano para múltiples usos.

+0

Totalmente cierto, pero no estoy seguro de que una expresión sea la mejor ruta aquí. Pero el ejemplo es bueno (por ejemplo). –

+0

+1 Agradable, Adam. – Ani

+0

Estoy marcando esto como la respuesta, ya que utiliza las clases Expression como la solución, que es lo que estaba buscando. Gracias Adam – Alex

7

Por supuesto, puede caminar el árbol de expresiones y luego usar Delegate.CreateDelegate para crear el Action<,> apropiado. Es muy sencillo, excepto por todas las comprobaciones de validación-(no estoy seguro si he cubierto todo):

No soy un experto expresión de árboles, pero no lo hago pensar la construcción de una expresión -tree y luego llamar al Compile es posible aquí ya que los árboles de expresión no pueden contener sentencias de asignación, hasta donde yo sé. (EDIT: Aparentemente, estos se han agregado en .NET 4. Es una característica difícil de encontrar ya que el compilador de C# no parece ser capaz de compilarlos a partir de lambdas).

public static Action<TContaining, TProperty> 
    CreateSetter<TContaining, TProperty> 
    (Expression<Func<TContaining, TProperty>> getter) 
{ 
    if (getter == null) 
     throw new ArgumentNullException("getter"); 

    var memberEx = getter.Body as MemberExpression; 

    if (memberEx == null) 
     throw new ArgumentException("Body is not a member-expression."); 

    var property = memberEx.Member as PropertyInfo; 

    if (property == null) 
     throw new ArgumentException("Member is not a property."); 

    if(!property.CanWrite) 
     throw new ArgumentException("Property is not writable."); 

    return (Action<TContaining, TProperty>) 
      Delegate.CreateDelegate(typeof(Action<TContaining, TProperty>), 
            property.GetSetMethod()); 
} 

Uso:

public class Person { public int Age { get; set; } } 

... 

static void Main(string[] args) 
{ 
    var setter = CreateSetter((Person p) => p.Age); 
    var person = new Person(); 
    setter(person, 25); 

    Console.WriteLine(person.Age); // 25  
} 

tenga en cuenta que esto crea un delegado ejemplo abierta, lo que significa que no está ligada a ninguna instancia particular de TContaining. Es simple modificarlo para vincularlo a una instancia específica; También deberá pasar un TContaining al método y luego usar una sobrecarga diferente de Delegate.CreateDelegate. La firma del método sería entonces algo como:

public static Action<TProperty> CreateSetter<TContaining, TProperty> 
     (Expression<Func<TContaining, TProperty>> getter, TContaining obj) 
+2

No estoy seguro, pero creo que agregaron asignaciones en .net 4. Pero el colocador ya debería tener la firma correcta, por lo que no es necesario. – CodesInChaos

+2

Sí, la tarea está en 4.0, pero realmente no la necesita aquí; Delegate.CreateDelegate es ideal. +1 –

+0

@CodeInChaos, @Marc Gravell: Gracias, eso fue nuevo para mí. – Ani

4

Punteros solamente me temo (no estoy en un pc) - pero;

  • .Body del lambda será muy probablemente MemberExpression
  • hacer un molde de seguridad (as etc.) y acceder a la .Member
  • ya que belive esta una propiedad, esto debe ser una PropertyInfo, de modo de prueba/molde etc
  • de un PropertyInfo, llame GetSetMethod() para obtener el correspondiente MethodInfo
  • uso Delegate.CreateDelegate para conseguir que como delegado (pasando el tipo de acción)
  • definitiva Ly, echó el Delegado regresó a la espera tipo de delegado
+1

Grandes mentes ... – Ani

Cuestiones relacionadas