【问题标题】:Using nested collections of lambda expressions to create an object graph使用 lambda 表达式的嵌套集合创建对象图
【发布时间】:2013-05-16 04:10:07
【问题描述】:

我对利用 lambda 表达式创建属性选择器树感兴趣。

使用场景是我们有一些代码在对象图上进行一些递归反射,为了限制递归的范围,我们目前使用 Attributes 来标记应该遍历哪些属性。即获取对象的所有修饰属性,如果该属性是具有修饰属性的引用类型,则对每个都重复。

使用属性的限制是您只能将它们放置在您控制其来源的类型上。 lambda 表达式树允许在任意类型的公共成员上定义范围。

用一种简写的方式来定义这些表达式会很方便,它反映了对象图的结构。

最终,我很想拥有这样的东西:

Selector<MyType> selector = new [] {
        (t => Property1),
        (t => Property2)
        {
                p => NestedProperty1,
                p => NestedProperty2
        }
};

现在,我能做的最好的事情就是明确地为每个节点声明一个实例,如下所示:

var selector = new Selector<MyType>()
{
    new SelectorNode<MyType, Property1Type>(t => Property1),
    new SelectorNode<MyType, Property2Type>(t => Property2)
    {
        new SelectorNode<Property2Type, NestedProperty1Type>(p => NestedProperty1),
        new SelectorNode<Property2Type, NestedProperty2Type>(p => NestedProperty2)
    },
};

这段代码没有任何问题,但是您必须显式地写出每个节点的类型参数,因为编译器无法推断类型参数。这是一种痛苦。而且丑陋。我已经看到了一些令人难以置信的语法糖,并且确信一定有更好的方法。

由于我对“高级”C# 概念(如动力学、协/逆变泛型和表达式树)缺乏了解,我想我会提出这个问题,看看是否有专家知道实现这一目标的方法(或有点像?)


作为参考,这些是实现我在帖子中描述的结构的 SelectorSelectorNode 类的声明:

public interface ISelectorNode<T> {}

public class Selector<T>: List<ISelectorNode<T>>{}

public class SelectorNode<T, TOut>: List<ISelectorNode<TOut>>, ISelectorNode<T> 
{
    public SelectorNode(Expression<Func<T, TOut>> select) {}
}

//Examples of Usage below

public class Dummy
{
    public ChildDummy Child { get; set; }
}

public class ChildDummy
{
    public string FakeProperty { get; set; }
}

public class Usage
{
    public Usage()
    {
        var selector = new Selector<Dummy>
        {
            new SelectorNode<Dummy, ChildDummy>(m => m.Child)
            {
                new SelectorNode<ChildDummy, string>(m => m.FakeProperty)
            }
        };
    }
}

为了扩展 nawal 的答案而进行了编辑:

利用 C# 的集合初始化语法,我们可以得到如下代码:

var selector = new Selector<Dummy>
  {
      (m => m.Child),
      {dummy => dummy.Child, 
          c => c.FakeProperty,
          c => c.FakeProperty                    
      }
  };

如果我们的 SelectorNode 类的 Add 方法如下所示:

public class Selector<T> : List<ISelectorNode<T>>
{
    public SelectorNode<T, T, TOut> Add<TOut>(Expression<Func<T, TOut>> selector, params Expression<Func<TOut, object>>[] children)
    {
        return SelectorNode<T, T, TOut>.Add(this, this, selector);
    }
}

一定有办法利用这种语法!

【问题讨论】:

  • 我认为有办法。你能告诉我们你的 Selector 和 SelectorNode 类吗?我的意思是类型定义和构造函数会做。
  • 酷,我编辑了帖子以包含类型定义和一些用法示例。

标签: c# c#-4.0 generics lambda type-inference


【解决方案1】:

编辑:我的以下回答无法回答这个问题。我以某种方式误读了它。我将提供另一个实际上可以完成这项工作的答案。保持这个答案是开放的,因为它可能会在将来帮助某人做一些相关的事情。


这是您可以通过流畅的界面进行管理的事情,但可能不适合您。

让你的选择器类是这样的:

public class Selector<T> : List<ISelectorNode<T>>
{
    public SelectorNode<T, TOut> Add<TOut>(Expression<Func<T, TOut>> selector)
    {
        return SelectorNode<T, TOut>.Add(this, selector);
    }
}



public class SelectorNode<T, TOut> : List<ISelectorNode<TOut>>, ISelectorNode<T>
{
    //move this common functionality to a third static class if it warrants.
    internal static SelectorNode<T, TOut> Add(List<ISelectorNode<T>> list, Expression<Func<T, TOut>> selector)
    {
        var node = new SelectorNode<T, TOut>(selector);
        list.Add(node);
        return node;
    }



    SelectorNode(Expression<Func<T, TOut>> selector) //unhide if you want it.
    {

    }



    public SelectorNode<TOut, TNextOut> Add<TNextOut>(Expression<Func<TOut, TNextOut>> selector)
    {
        return SelectorNode<TOut, TNextOut>.Add(this, selector);
    }
}

现在你可以打电话了:

var selector = new Selector<Dummy>();
selector.Add(m => m.Child).Add(m => m.FakeProperty); //just chain the rest..

我个人认为这比您在问题中的方法更具可读性,但没有那么直观或令人讨厌:) 我认为您不能将它放在一行中(可悲的是:(),但可能会有困难的方法.

更新:

单行:

public class Selector<T> : List<ISelectorNode<T>>
{
    public SelectorNode<T, T, TOut> Add<TOut>(Expression<Func<T, TOut>> selector)
    {
        return SelectorNode<T, T, TOut>.Add(this, this, selector);
    }
}



public class SelectorNode<S, T, TOut> : List<ISelectorNode<TOut>>, ISelectorNode<T>
{
    //move this common functionality to a third static class if it warrants.
    internal static SelectorNode<S, T, TOut> Add(Selector<S> parent, List<ISelectorNode<T>> list, 
                                                 Expression<Func<T, TOut>> selector)
    {
        var node = new SelectorNode<S, T, TOut>(parent, selector);
        list.Add(node);
        return node;
    }



    Selector<S> parent;

    SelectorNode(Selector<S> parent, Expression<Func<T, TOut>> selector) //unhide if you want it.
    {
        this.parent = parent;
    }



    public SelectorNode<S, TOut, TNextOut> Add<TNextOut>(Expression<Func<TOut, TNextOut>> selector)
    {
        return SelectorNode<S, TOut, TNextOut>.Add(parent, this, selector);
    }

    public Selector<S> Finish()
    {
        return parent;
    }
}

用法:

var selector = new Selector<Dummy>().Add(m => m.Child).Add(m => m.FakeProperty).Finish();

//or the earlier

var selector = new Selector<Dummy>();
selector.Add(m => m.Child).Add(m => m.FakeProperty); //just chain the rest, no need of Finish

第一种方法的优点:

  1. 更简单

  2. 不改变现有定义(SelectorNode

秒的优势:

  1. 提供更清洁的通话。

这两种方法的一个小缺点可能是现在您有一个内部静态方法Add 用于共享公共功能,这在这两个选择器类之外没有任何意义,但我猜这很适合。您可以删除方法和重复代码(或者硬方法,将SelectorNode 嵌套在Selector 中,如果SelectorNodeSelector 类之外没有意义,则将实现隐藏到外部世界。或者更糟糕的是使其受保护并继承一个其他类)

一个建议:您很可能希望以 composition 方式而不是 inheritance 方式使用您的 List&lt;T&gt;s。您的类名(选择器)不知道它下面的集合。顺便说一句,好问题!

【讨论】:

  • 哇,我从未见过这样使用内部静态方法!我实际上不明白这是如何工作的,我一直认为你不能在 Constructor 调用上链接方法。你能告诉我这究竟是如何工作的吗?
  • @Martaver 我很想知道你最终用这个实现了什么。在我看来,利用编译时安全和反射是一个好主意。这是开源的吗(如果是的话,是否有项目链接)?
  • @Martaver 我可以,我需要在 cmets 中给出它,还是更新答案?其实很简单。您可以做的最简单的操作是将我的代码复制粘贴到您的 VS,然后使用 F11 使用一些示例值进行调试。您将看到指针如何逐行移动:)
  • 嗨,伙计,很抱歉这么久才回复您。我在 VS 中启动了你的代码,它非常聪明。我喜欢将指针链接到子级的流畅方法,但问题是(据我所见)这是线性的。您转到 Parent > Child > Property,然后必须调用 Finish 才能将流畅性的范围返回给“Parent”。我想知道是否有可能以这样的方式执行此操作,即在选择器的 params 集合中将子项定义为 add 方法的参数。这样可以为每个“添加”调用定义多个子级?
  • 我已经编辑了我的问题以包含一些代码来展示我的想法,如果我们使用自定义的 Add 方法、表达式和 C# 集合初始化程序,也许我们可以更进一步?
【解决方案2】:

我不得不承认在这个阶段我已经麻木地思考了太多的选择,希望这是我的最后一个.. :)

最后,您在问题中提到的那个 - Expression&lt;Func&lt;T, object&gt;&gt; 路线。我不知道如何在不失去一些编译时安全性的情况下改善这一点。与我的第一个答案非常相似:

public class Selector<T> : List<ISelectorNode<T>>
{
    public static SelectorNode<T, TOut> Get<TOut>(Expression<Func<T, TOut>> selector)
    {
        return new SelectorNode<T, TOut>(selector);
    }

    public void Add<TOut>(Expression<Func<T, TOut>> selector)
    {
        var node = new SelectorNode<T, TOut>(selector);
        Add(node);
    }
}



public class SelectorNode<T, TOut> : List<ISelectorNode<TOut>>, ISelectorNode<T>
{

    public SelectorNode(Expression<Func<T, TOut>> selector)
    {

    }

    public ISelectorNode<T> Add(params Expression<Func<TOut, object>>[] selectors)
    {
        foreach (var selector in selectors)
            base.Add(new SelectorNode<TOut, object>(selector));

        return this;
    }

    public ISelectorNode<T> Add(params ISelectorNode<TOut>[] nodes)
    {
        AddRange(nodes);
        return this;
    }
}

你打电话给:

var selector = new Selector<Person>
{
    Selector<Person>.Get(m => m.Address).Add
    (
        Selector<Address>.Get(x => x.Place),
        Selector<Address>.Get(x => x.ParentName).Add
        (
            x => x.Id,
            x => x.FirstName,
            x => x.Surname
        )
    ),

    Selector<Person>.Get(m => m.Name).Add
    (
        x => x.Id,
        x => x.FirstName,
        x => x.Surname
    ),

    m => m.Age
};

到目前为止,这是我最喜欢的(如果有用的话)..

【讨论】:

    【解决方案3】:

    您的实际实现非常干净易读,可能会根据您的喜好有点冗长 - 问题源于 collection 初始化程序糖仅在实例化集合实例时才有效(在构造函数上使用 new 关键字当然),可悲的是C# doesn't infer type from the constructor现在至少在某种程度上排除了你想要做的事情。

    还有这样的语法

    (m => m.Child)
        .SomeAddMethod(c => c.FakeProperty)
    

    即使您在Expression&lt;Func&lt;T, TOut&gt;&gt; 上有扩展方法SomeAddMethod,也不能工作without explicitly stating what the lambda actually stands for。我不得不说这些有时是皮塔饼。

    可以做的是最小化类型规范。最常见的方法是创建一个静态类,这将要求您提供形参类型(在您的情况下为 T)和来自参数 Expression&lt;Func&lt;T, TOut&gt;&gt;once the formal parameter type is known the return type (TOut) will be inferred

    让我们一步一步来。考虑一个更复杂的类层次结构:

    public class Person
    {
        public Address Address { get; set; }
        public Name Name { get; set; }
        public int Age { get; set; }
    }
    
    public class Address
    {
        public string Place { get; set; }
        public Name ParentName { get; set; }
    }
    
    public class Name
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string Surname { get; set; }
    }
    

    假设你有这个(最简单的):

    public class Selector<T> : List<ISelectorNode<T>>
    {
        public static SelectorNode<T, TOut> Get<TOut>(Expression<Func<T, TOut>> selector)
        {
            return new SelectorNode<T, TOut>(selector);
        }
    }
    
    
    
    public class SelectorNode<T, TOut> : List<ISelectorNode<TOut>>, ISelectorNode<T>
    {
        internal SelectorNode(Expression<Func<T, TOut>> selector)
        {
    
        }
    }
    

    现在您可以手动添加所有内容,但输入的参数要少得多。像这样的:

    var selector = new Selector<Person>();
    
    var pA = Selector<Person>.Get(m => m.Address);
        var aS = Selector<Address>.Get(m => m.Place);
        var aN = Selector<Address>.Get(m => m.ParentName);
            var nI1 = Selector<Name>.Get(m => m.Id);
            var nS11 = Selector<Name>.Get(m => m.FirstName);
            var nS12 = Selector<Name>.Get(m => m.Surname);
    
    var pN = Selector<Person>.Get(m => m.Name);
        var nI2 = Selector<Name>.Get(m => m.Id);
        var nS21 = Selector<Name>.Get(m => m.FirstName);
        var nS22 = Selector<Name>.Get(m => m.Surname);
    
    var pI = Selector<Person>.Get(m => m.Age);
    
    selector.Add(pA);
        pA.Add(aS);
        pA.Add(aN);
            aN.Add(nI1);
            aN.Add(nS11);
            aN.Add(nS12);
    
    selector.Add(pN);
        pN.Add(nI2);
        pN.Add(nS21);
        pN.Add(nS22);
    
    selector.Add(pI);
    

    非常简单,但不那么直观(我会更喜欢您的原始语法)。也许我们可以缩短这个:

    public class Selector<T> : List<ISelectorNode<T>>
    {
        public static SelectorNode<T, TOut> Get<TOut>(Expression<Func<T, TOut>> selector)
        {
            return new SelectorNode<T, TOut>(selector);
        }
    
        public Selector<T> Add(params ISelectorNode<T>[] nodes)
        {
            AddRange(nodes);
            return this;
        }
    }
    
    
    
    public class SelectorNode<T, TOut> : List<ISelectorNode<TOut>>, ISelectorNode<T>
    {
        internal SelectorNode(Expression<Func<T, TOut>> selector)
        {
    
        }
    
    
    
        public ISelectorNode<T> Add(params ISelectorNode<TOut>[] nodes)
        {
            AddRange(nodes);
            return this;
        }
    }
    

    现在你可以打电话了:

    var selector = new Selector<Person>().Add
    (
        Selector<Person>.Get(m => m.Address).Add
        (
            Selector<Address>.Get(x => x.Place),
            Selector<Address>.Get(x => x.ParentName).Add
            (
                Selector<Name>.Get(x => x.Id),
                Selector<Name>.Get(x => x.FirstName),
                Selector<Name>.Get(x => x.Surname)
            )
        ), 
        Selector<Person>.Get(m => m.Name).Add
        (
            Selector<Name>.Get(x => x.Id),
            Selector<Name>.Get(x => x.FirstName),
            Selector<Name>.Get(x => x.Surname)
        ),
        Selector<Person>.Get(m => m.Age)
    );
    

    更简洁,但我们可以使用集合初始化器语法使其看起来更好一些。 Selector&lt;T&gt; 中不需要 Add(params) 方法,你会得到:

    public class Selector<T> : List<ISelectorNode<T>>
    {
        public static SelectorNode<T, TOut> Get<TOut>(Expression<Func<T, TOut>> selector)
        {
            return new SelectorNode<T, TOut>(selector);
        }
    }
    
    var selector = new Selector<Person>
    {
        Selector<Person>.Get(m => m.Address).Add
        (
            Selector<Address>.Get(x => x.Place),
            Selector<Address>.Get(x => x.ParentName).Add
            (
                Selector<Name>.Get(x => x.Id),
                Selector<Name>.Get(x => x.FirstName),
                Selector<Name>.Get(x => x.Surname)
            )
        ),
        Selector<Person>.Get(m => m.Name).Add
        (
                Selector<Name>.Get(x => x.Id),
            Selector<Name>.Get(x => x.FirstName),
            Selector<Name>.Get(x => x.Surname)
        ),
        Selector<Person>.Get(m => m.Age)
    };
    

    通过在Selector&lt;T&gt; 中添加另一个Add 重载,如下所示,您可以减少更多输入,但这太疯狂了:

    public class Selector<T> : List<ISelectorNode<T>>
    {
        public static SelectorNode<T, TOut> Get<TOut>(Expression<Func<T, TOut>> selector)
        {
            return new SelectorNode<T, TOut>(selector);
        }
    
        public void Add<TOut>(Expression<Func<T, TOut>> selector)
        {
            var node = new SelectorNode<T, TOut>(selector);
            Add(node);
        }
    }
    
    var selector = new Selector<Person>
    {
        Selector<Person>.Get(m => m.Address).Add
        (
            Selector<Address>.Get(x => x.Place),
            Selector<Address>.Get(x => x.ParentName).Add
            (
                Selector<Name>.Get(x => x.Id),
                Selector<Name>.Get(x => x.FirstName),
                Selector<Name>.Get(x => x.Surname)
            )
        ),
    
        Selector<Person>.Get(m => m.Name).Add
        (
            Selector<Name>.Get(x => x.Id),
            Selector<Name>.Get(x => x.FirstName),
            Selector<Name>.Get(x => x.Surname)
        ),
    
        m => m.Age // <- the change here
    };
    

    这是因为集合初始化器可以调用不同的Add 重载。但我个人更喜欢之前通话的一贯风格。

    【讨论】:

      【解决方案4】:

      更多(集合初始化程序)糖混乱:

      public class Selector<T> : List<ISelectorNode<T>>
      {
          public void Add(params Selector<T>[] selectors)
          {
              Add(this, selectors);
          }
      
          static void Add<TOut>(List<ISelectorNode<TOut>> nodes, Selector<TOut>[] selectors)
          {
              foreach (var selector in selectors)
                  nodes.AddRange(selector);
      
              //or just, Array.ForEach(selectors, nodes.AddRange);
          }
      
          public void Add<TOut>(Expression<Func<T, TOut>> selector)
          {
              var node = new SelectorNode<T, TOut>(selector);
              Add(node);
          }
      
          //better to have a different name than 'Add' in cases of T == TOut collision - when classes 
          //have properties of its own type, eg Type.BaseType
          public Selector<T> InnerAdd<TOut>(params Selector<TOut>[] selectors)
          {
              foreach (SelectorNode<T, TOut> node in this)
                  Add(node, selectors);
      
              //or just, ForEach(node => Add((SelectorNode<T, TOut>)node, selectors));
              return this;
          }
      }
      
      public class SelectorNode<T, TOut> : List<ISelectorNode<TOut>>, ISelectorNode<T>
      {
          internal SelectorNode(Expression<Func<T, TOut>> selector)
          {
      
          }
      }
      

      现在这样称呼它:

      var selector = new Selector<Person>
      {
          new Selector<Person>
          {
              m => m.Address
          }.InnerAdd
          (
              new Selector<Address>
              {
                  n => n.Place
              },
              new Selector<Address>
              {
                  n => n.ParentName
              }.InnerAdd
              (
                  new Selector<Name>
                  {
                      o => o.Id,
                      o => o.FirstName,
                      o => o.Surname
                  }
              )
          ),
      
          new Selector<Person>
          {
              m => m.Name
          }.InnerAdd
          (
              new Selector<Name>
              {
                  n => n.Id,
                  n => n.FirstName,
                  n => n.Surname
              }
          ),
      
          m => m.Age
      };
      

      这有帮助吗?我不这么认为。非常令人讨厌,但不太直观。更糟糕的是,没有固有的类型安全性(这完全取决于您为 Selector&lt;T&gt; 集合初始化程序提供的类型)。

      【讨论】:

        【解决方案5】:

        还有一个 - 根本没有类型规范,但很丑:)

        static class Selector
        {
            //just a mechanism to share code. inline yourself if this is too much abstraction
            internal static S Add<R, S, T, TOut>(R list, Expression<Func<T, TOut>> selector,
                                                 Func<SelectorNode<T, TOut>, S> returner) where R : List<ISelectorNode<T>>
            {
                var node = new SelectorNode<T, TOut>(selector);
                list.Add(node);
                return returner(node);
            }
        }
        
        
        
        public class Selector<T> : List<ISelectorNode<T>>
        {
            public Selector<T> AddToConcatRest<TOut>(Expression<Func<T, TOut>> selector)
            {
                return Selector.Add(this, selector, node => this);
            }
        
            public SelectorNode<T, TOut> AddToAddToItsInner<TOut>(Expression<Func<T, TOut>> selector)
            {
                return Selector.Add(this, selector, node => node);
            }
        }
        
        
        
        public class SelectorNode<T, TOut> : List<ISelectorNode<TOut>>, ISelectorNode<T>
        {
            internal SelectorNode(Expression<Func<T, TOut>> selector)
            {
        
            }
        
        
        
            public SelectorNode<T, TOut> InnerAddToConcatRest<TNextOut>(Expression<Func<TOut, TNextOut>> selector)
            {
                return AddToConcatRest(selector);
            }
        
            public SelectorNode<TOut, TNextOut> InnerAddToAddToItsInnerAgain<TNextOut>(Expression<Func<TOut, TNextOut>> selector)
            {
                return AddToAddToItsInner(selector);
            }
        
            //or just 'Concat' ?
            public SelectorNode<T, TOut> AddToConcatRest<TNextOut>(Expression<Func<TOut, TNextOut>> selector)
            {
                return Selector.Add(this, selector, node => this);
            }
        
            public SelectorNode<TOut, TNextOut> AddToAddToItsInner<TNextOut>(Expression<Func<TOut, TNextOut>> selector)
            {
                return Selector.Add(this, selector, node => node);
            }
        }
        

        我为函数提供了描述性名称,以明确意图。我不打算详细解释它们各自的作用,我想函数名就足够了。继续上一个例子:

        var selector = new Selector<Person>();
        
        var pA = selector.AddToAddToItsInner(m => m.Address);
            var aN = pA.InnerAddToConcatRest(m => m.Place);
            var aS = aN.AddToAddToItsInner(m => m.ParentName);
                var nI1 = aS.InnerAddToConcatRest(m => m.Id);
                var nS11 = nI1.AddToConcatRest(m => m.FirstName);
                var nS12 = nS11.AddToConcatRest(m => m.Surname);
        
        var pN = selector.AddToAddToItsInner(m => m.Name);
            var nI2 = pN.InnerAddToConcatRest(m => m.Id);
            var nS21 = nI2.AddToConcatRest(m => m.FirstName);
            var nS22 = nS21.AddToConcatRest(m => m.Surname);
        
        var pI = selector.AddToConcatRest(m => m.Age);
        

        或提供替代方案(将想法带回家):

        var selector = new Selector<Person>();
        
        var pA = selector.AddToAddToItsInner(m => m.Address);
            var aS = pA.InnerAddToConcatRest(m => m.Place);
            var aN = pA.InnerAddToAddToItsInnerAgain(m => m.ParentName);
                var nI1 = aN.InnerAddToConcatRest(m => m.Id);
                var nS11 = nI1.AddToConcatRest(m => m.FirstName);
                var nS12 = nS11.AddToConcatRest(m => m.Surname);
        
        var pN = selector.AddToAddToItsInner(m => m.Name);
            var nI2 = pN.InnerAddToConcatRest(m => m.Id);
            var nS21 = nI2.AddToConcatRest(m => m.FirstName);
            var nS22 = nS21.AddToConcatRest(m => m.Surname);
        
        var pI = selector.AddToConcatRest(m => m.Age);
        

        现在我们可以结合起来使其简洁并省略多余的变量:

        var selector = new Selector<Person>();
        
        selector.AddToConcatRest(m => m.Age).AddToAddToItsInner(m => m.Address)
                    .InnerAddToConcatRest(m => m.Place).AddToAddToItsInner(m => m.ParentName)
                        .InnerAddToConcatRest(m => m.Id).AddToConcatRest(m => m.FirstName).AddToConcatRest(m => m.Surname);
        
        selector.AddToAddToItsInner(m => m.Name)
                    .InnerAddToConcatRest(m => m.Id).AddToConcatRest(m => m.FirstName).AddToConcatRest(m => m.Surname);
        

        现在您可能已经注意到许多Add 函数在内部执行相同的工作。我将这些方法分开是因为从调用方来看,它们有不同的语义要执行。如果您可以知道它的作用/含义,那么代码可以再次变得更短。将SelectorNode&lt;,&gt; 类更改为:

        public class SelectorNode<T, TOut> : List<ISelectorNode<TOut>>, ISelectorNode<T>
        {
            internal SelectorNode(Expression<Func<T, TOut>> selector)
            {
        
            }
        
        
        
            public SelectorNode<T, TOut> Add<TNextOut>(Expression<Func<TOut, TNextOut>> selector)
            {
                return Selector.Add(this, selector, node => this);
            }
        
            public SelectorNode<TOut, TNextOut> AddToAddToItsInner<TNextOut>(Expression<Func<TOut, TNextOut>> selector)
            {
                return Selector.Add(this, selector, node => node);
            }
        }
        

        现在用法:

        var selector = new Selector<Person>();
        selector.AddToConcatRest(m => m.Age).AddToAddToItsInner(m => m.Address)
                    .Add(m => m.Place).AddToAddToItsInner(m => m.ParentName)
                        .Add(m => m.Id).Add(m => m.FirstName).Add(m => m.Surname);
        selector.AddToAddToItsInner(m => m.Name)
                    .Add(m => m.Id).Add(m => m.FirstName).Add(m => m.Surname);
        

        当您选择多种方法的组合时,可能还有很多其他选择。在这种方法链接的特殊情况下,如果这让调用者感到困惑,另一种可能性是从调用者端盲目添加并在内部丢弃重复项。像这样的:

        var selector = new Selector<Person>();
        selector.Add(m => m.Address).Add(m => m.Place);
        selector.Add(m => m.Address).Add(m => m.ParentName).Add(m => m.Id); //at this stage discard duplicates
        selector.Add(m => m.Address).Add(m => m.ParentName).Add(m => m.FirstName); //and so on
        selector.Add(m => m.Name)... etc
        selector.Add(m => m.Age);
        

        为此,您必须为节点类引入自己的相等比较器,这使得它非常脆弱。

        另一种直观的方法是直接在表达式中链接属性。喜欢:

        selector.Add(m => m.Address.Place);
        selector.Add(m => m.Address.ParentName.Id);
        selector.Add(m => m.Address.ParentName.FirstName); // and so on.
        

        在内部,您需要将表达式分解为多个部分,并基于它们构建您自己的表达式。如果我有时间,我会在稍后的某个阶段给出答案。

        【讨论】:

          【解决方案6】:

          我要问你的一件事是,为什么不使用反射来避免提供参数的麻烦呢?您可以使用递归遍历节点(属性)并从那里手动构建树(参见线程thisthis)。不过,这可能无法为您提供您想要的那种灵活性。

          表达式不是我的强项,所以把它当作伪代码。在这里你肯定有更多的工作要做。

          public class Selector<T> : List<ISelectorNode<object>>
          {
              public Selector()
              {
                  Add(typeof(T), this);
              }
          
              void Add(Type type, List<ISelectorNode<object>> nodes)
              {
                  foreach (var property in type.GetProperties()) //with whatever flags
                  {
                      //the second argument is a cool param name I have given, discard-able 
                      var paramExpr = Expression.Parameter(type, type.Name[0].ToString().ToLower()); 
                      var propExpr = Expression.Property(paramExpr, property);
          
                      var innerNode = new SelectorNode(Expression.Lambda(propExpr, paramExpr));
                      nodes.Add(innerNode);
                      Add(property.PropertyType, innerNode);
                  }
              }
          }
          
          
          
          public class SelectorNode : List<ISelectorNode<object>>, ISelectorNode<object>
          {
          
              internal SelectorNode(LambdaExpression selector)
              {
          
              }
          }
          

          及用法:

          var selector = new Selector<Person>();
          

          就是这样。这将产生您可能不想要的那些属性,例如 DateTimestring 等内置类型的属性,但我认为绕过它们很简单。或者更好的是,您可以创建自己的规则并传递它们以确定遍历应该如何发生。

          【讨论】:

            猜你喜欢
            • 2018-05-14
            • 2018-08-07
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-09-02
            • 1970-01-01
            • 2016-07-13
            相关资源
            最近更新 更多