使用嵌套的lambda表达式集合来创建对象图

时间:2013-05-16 04:10:08

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

我对利用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)
            }
        };
    }
}

为了扩大纳瓦尔的答案而编辑:

利用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);
    }
}

必须有办法利用这种语法!

6 个答案:

答案 0 :(得分:3)

编辑:我的下面的答案不可原谅地没有回答这个问题。我以某种方式误解了它。我将提供另一个可以实际完成工作的答案。保持这个答案是开放的,因为这可能会帮助将来某人做出相关的事情。


这是您可以使用流畅的界面管理的内容,但可能无法为您完成。

让您的选择器类如下:

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

  3. 第二个优点:

    1. 提供更清洁的电话。
    2. 这两种方法的一个小缺点可能是,现在你有一个内部静态方法Add用于共享通用功能,这些功能在这两个选择器类之外没有任何意义,但我认为这是宜居的。如果SelectorNodeSelector类之外没有任何意义,您可以删除方法并重复代码(或者难以在SelectorNode内嵌套Selector并将实现隐藏到外部世界。或者更糟糕的是让它受到保护并从另一个类继承一个类

      建议:您可能最希望使用继承的方式使用继承方式使用List<T>。您的类名(选择器)不会知道它下面的集合。好问题顺便说一句!

答案 1 :(得分:1)

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

最后,您在问题中提到的那个 - Expression<Func<T, object>>路线。我不知道如何在不损失编译时安全性的情况下更好地完成这项工作。与我的第一个答案非常相似:

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
};

所有这些都是我的恩惠,直到现在(如果有的话)..

答案 2 :(得分:0)

您的实际实现非常干净且可读,可能有点令您满意 - 问题源于集合初始化程序糖仅在实例化集合实例时使用new关键字当然是构造函数)而且遗憾C# doesn't infer type from the constructor现在排除了你想要做的事情,至少在某种程度上。

这样的语法

(m => m.Child)
    .SomeAddMethod(c => c.FakeProperty)
即使您在SomeAddMethod上有扩展方法Expression<Func<T, TOut>>

仍然无效without explicitly stating what the lambda actually stands for。我不得不说这些有时候是皮塔饼。

可以做的是最小化类型规范。最常见的方法是创建一个静态类,要求您从参数{{1}中提供形式参数类型(在您的情况下为T)和once the formal parameter type is known the return type (TOut) will be inferred }。

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

Expression<Func<T, TOut>>

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

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) ); 中不需要Add(params)方法,你得到:

Selector<T>

如下所示,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) }; 中的另一个Add重载可以减少更多的输入,但这很疯狂:

Selector<T>

这是有效的,因为集合初始值设定项可以调用不同的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 }; 重载。但我个人更喜欢以前称呼的一贯风格。

答案 3 :(得分:0)

更多(收集初始化)糖混乱:

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<T>集合初始化程序提供的类型)。

答案 4 :(得分:0)

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

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<,>课程更改为:

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.

在内部,你需要将表达式分解为碎片并根据它们构建自己的表达式。如果我有时间,我会在稍后阶段做出答案。

答案 5 :(得分:0)

我要问你的一件事是为什么不使用反射并避免提供参数的麻烦?您可以使用递归遍历节点(属性)并从那里手动构建树(请参阅线程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等内置类型的属性,但我认为绕过它们是微不足道的。或者更好的是,您可以创建自己的规则并传递它们以确定遍历应该如何发生。