【问题标题】:How to correctly use Expression Trees on properties (without reflection)如何在属性上正确使用表达式树(无反射)
【发布时间】:2016-06-07 13:37:49
【问题描述】:

目标:处理一个对象,如果该对象实现了预期的类型,我想更改一个特定的属性值(这部分工作正常),我也想应用相同的逻辑到所有具有相同预期类型的​​属性列表(我明确指出)。

我有以下代码:

public abstract class BaseObject
{
    public int Id { get; set; }
}

public class Father : BaseObject
{
    public DateTime CreatedOn { get; set; }
    public string Name { get; set; }
    public IEnumerable<ChildA> Children1 { get; set; }
    public IEnumerable<ChildB> Children2 { get; set; }
    public IEnumerable<ChildA> Children3 { get; set; }
    public IEnumerable<ChildB> Children4 { get; set; }
}

public class ChildA : BaseObject
{
    public int Val1 { get; set; }
}

public class ChildB : BaseObject
{
    public string Name { get; set; }
    public int Total { get; set; }
}

我想通过对目标对象的特定属性和我明确说的所有属性子项应用一些更改来处理对象:

public void Start()
{
    var listA = new List<ChildA> { new ChildA { Id = 1, Val1 = 1 }, new ChildA { Id = 2, Val1 = 2 } };
    var listB = new List<ChildB> { new ChildB { Id = 1, Name = "1", Total = 1 } };
    var obj = new Father { Id = 1, CreatedOn = DateTime.Now, Name = "F1", ChildrenA = listA, ChildrenB = listB };

    // I explicit tell to process only 2 of the 4 lists....
    ProcessObj(obj, x => new object[] { x.Children1, x.Children2 });            
}

我能写出这个函数:

public void ProcessObj<T>(T obj, Expression<Func<T, object[]>> includes = null)
{
    var objBaseObject = obj as BaseObject;
    if (objBaseObject == null) return;

    // Here I change the ID - add 100 just as an example....
    objBaseObject.Id = objBaseObject.Id + 100;

    if (includes == null) return;

    var array = includes.Body as NewArrayExpression;
    if (array == null) return;

    var exps = ((IEnumerable<object>)array.Expressions).ToArray();
    for (var i = 0; i < exps.Count(); i++)
    {
        var name = ((MemberExpression)exps[i]).Member.Name;
        var childProperty = obj.GetType().GetProperties(
                BindingFlags.Public | BindingFlags.Instance
               ).FirstOrDefault(prop => prop.Name == name);
        if (childProperty == null) continue;

        // NOT correct because I think I am getting a copy of the object 
        // and not pointing to the object in memory (by reference)
        var childList = childProperty.GetValue(obj); 

        // TODO: loop on the list and apply the same logic as the father.... 
        // change the ID field....
    }
}

在这个原型中我开始写反射,但如果可能的话我真的很想避免它....

我该怎么做???

【问题讨论】:

  • 这是一个旁白,但为了该方法的实用性,既然它只对 BaseObject 的 T 做任何事情,为什么不将where T : BaseObject 添加到您的方法中?
  • 当然,也可以添加,谢谢指点。
  • 为什么还需要整个带有反射的繁琐程序(表达式树也是一种反射,毫无疑问)?在上面所有代码中执行有用工作的唯一行是objBaseObject.Id = objBaseObject.Id + 100。你有什么理由不能将包含它的委托传递给foreach,它只是在编译时把事情搞砸了?如果你必须递归工作,也许写一个访问者?
  • @JeroenMostert 我写了表达式列表是为了不传递字符串数组。关于委托的“无用”,因为我想将其应用于更复杂的情况,我认为不需要将委托作为参数传递会很好(也因为稍后我想让它递归)。 ..而且我不想为每个目标列表写一个代表。也许它可以更容易地做到这一点,但我缺乏这方面的知识...... :(
  • 我相信你有一个非常愚蠢的对象,因此需要由非常聪明的代码来支持。您可能需要考虑让您的对象不那么愚蠢,并为他们提供方法来做一些有助于了解其上下文的事情 (Father.ApplyOneTwo(b =&gt; b.ID += 100))。但是,这进入了代码审查领域,这实际上并不在 SO 的范围内。一个仍然相关的问题:所有你想要对在编译时确定的对象做什么?如果是这样,可以避免反射,否则不能。

标签: c# generics lambda


【解决方案1】:

也许我遗漏了一些东西,但您似乎通过使用表达式树使问题复杂化了。您可以不使用常规的 Action 和 Func 代表来执行此操作吗?为什么它们需要是表达式树?这是一个仅使用委托的示例:

public void ProcessObj<T>(T obj, Func<T, IEnumerable<object>> includes) {
     var objBaseObject = obj as BaseObject;
    if (objBaseObject == null) return;


    // Create a reusable action to use on both the parent and the children
    Action<BaseObject> action = x => x.Id += 100;

    // Run the action against the root object
    action(objBaseObject);

    // Get the includes by just invoking the delegate. No need for trees.
    var includes = includes(obj);

    // Loop over each item in each collection. If the types then invoke the same action that we used on the root. 
    foreach(IEnumerable<object> include in includes) 
    {
       foreach(object item in include) 
       {
          var childBaseObject = item as BaseObject;
          if(childBaseObject != null) 
          {
            action(childBaseObject);          
          }
       }
    }
}

可以像以前一样使用:

ProcessObj(obj, x => new object[] { x.Children1, x.Children2 });

没有表达式树也没有反射,只有常规的委托 lambda。

希望有帮助

【讨论】:

  • 再次感谢 ;)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多