5

这就是我想要实现的目标:

config.Name("Foo")
      .Elements(() => {
                         Element.Name("element1").Height(23);
                         Element.Name("element2").Height(31);
                      })
     .Foo(23);

或像这样:

  .Elements(e => {
                     e.Name("element1").Height(23);
                     e.Name("element2").Height(31);
                  })
  .Foo(3232);

这就是我目前所拥有的:

public class Config
{
   private string name;
   private int foo;
   private IList<Element> elements = new List<Element>();

   public Config Name(string name)
   {
      this.name = name;
      return this;
   }

   public Config Foo(int x)
   {
       this.foo = x;
   }

   ... //add method for adding elements 

   class Element
   {
      public string Name { get; set; }
      public int Height { get; set; }
   }
}

有人知道该怎么做吗?

4

6 回答 6

5
public class Config
{
   private string name;
   private IList<Element> elements = new List<Element>();
   public IList<Element> GetElements {get {return this.elements;}}
   public Config Name(string name)
   {
      this.name = name;
      return this;
   }

   public Config Elements(IEnumerable<Element> list)
   {
        foreach ( var element in list)
            elements.Add(element);
        return this;
   }

   public Config Elements(params Element[] list)
   {
        foreach ( var element in list)
            elements.Add(element);
        return this;
   }

   public Config Elements(params Expression<Func<Element>>[] funcs)
   {
        foreach (var func in funcs )
            elements.Add(func.Compile()());
        return this;
   }

   public Config Elements(params Expression<Func<IEnumerable<Element>>>[] funcs)
   {
        foreach (var func in funcs )
            foreach ( var element in func.Compile()())
                elements.Add(element);
        return this;
   }

   public class Element
   {
      public string Name { get; set; }
      public int Height { get; set; }     
      public Element() {}
      public Element(string name)
      {
         this.Name = name;
      }  
      public Element AddHeight(int height)
      {
          this.Height = height;
          return this;
      }
      public static Element AddName(string name)
      {
        return new Element(name);
      }
   }
}

用法

var cfg = new Config()
    .Name("X")
    .Elements(new [] { new Config.Element { Name = "", Height = 0} })
    .Elements(
            Config.Element.AddName("1").AddHeight(1), 
            Config.Element.AddName("2").AddHeight(2) 
            )
    .Elements(
        () => Config.Element.AddName("1").AddHeight(1)
    )
    .Elements(
        () => new[] {
                Config.Element.AddName("1").AddHeight(1),
                Config.Element.AddName("1").AddHeight(1)
               }
    )
于 2012-04-27T09:02:05.620 回答
5

这是一个完全按照您的第二个代码示例工作的版本。虽然它真的很丑 - 我绝对不想自己使用它。注释在最后。

using System;
using System.Collections.Generic;

public class Config
{
    private string name;
    private int foo;
    private IList<Element> elements = new List<Element>();

    public Config Name(string name)
    {
        this.name = name;
        return this;
    }

    public Config Foo(int x)
    {
        this.foo = x;
        return this;
    }

    public Config Elements(Action<ElementBuilder> builderAction)
    {
        ElementBuilder builder = new ElementBuilder(this);
        builderAction(builder);
        return this;
    }

    public class ElementBuilder
    {
        private readonly Config config;

        internal ElementBuilder(Config config)
        {
            this.config = config;
        }

        public ElementHeightBuilder Name(string name)
        {
            Element element = new Element { Name = name };
            config.elements.Add(element);
            return new ElementHeightBuilder(element);
        }
    }

    public class ElementHeightBuilder
    {
        private readonly Element element;

        internal ElementHeightBuilder(Element element)
        {
            this.element = element;
        }

        public void Height(int height)
        {
            element.Height = height;
        }
    }

    public class Element
    {
        public string Name { get; set; }
        public int Height { get; set; }
    }
}



class Test
{
    static void Main()
    {
        Config config = new Config();

        config.Name("Foo")
            .Elements(e => {
                e.Name("element1").Height(23);
                e.Name("element2").Height(31);
            })
            .Foo(3232);
    }
}

笔记:

使用此代码,您必须先调用Name,然后可以选择调用Height每个元素 - 尽管如果您调用Height. 如果您将Elements呼叫更改为:

  .Elements(e => {
                     e.NewElement().Name("element1").Height(23);
                     e.NewElement().Name("element2").Height(31);
                 })

或这个:

  .Elements(e => {
                     e.Name("element1").Height(23).AddToConfig();
                     e.Name("element2").Height(31).AddToConfig();
                 })

那么你最终会得到一个更灵活的情况;你可以有一个ElementBuilder班级来做正确的事。这个的第一个版本是更好的 IMO。

所有这些仍然不如我的另一个答案中显示的简单有效的对象/集合初始化程序那么令人愉快,我强烈建议您使用它。我对这种方法真的没有任何好处——如果你没有在 Telerik API 中看到过,你自然会想要这个吗?从其他评论看来,您似乎被使用 lambda 表达式的“闪亮”所吸引……不要。它们在正确的环境中很棒,但在我看来,如果没有它们,有清洁的方法可以实现这一目标。

我建议您退后一步,弄清楚您是否真的从您最初想要使用的语法中获得了任何东西,并考虑您是否更愿意维护此答案中的代码类型,或者对象/集合初始化程序中的代码解决方案。

编辑:这是我对 Zoltar 建议的解释,它消除了对额外课程的需要:

using System;
using System.Collections.Generic;

public class Config
{
    private string name;
    private int foo;
    private IList<Element> elements = new List<Element>();

    public Config Name(string name)
    {
        this.name = name;
        return this;
    }

    public Config Foo(int x)
    {
        this.foo = x;
        return this;
    }

    public Config Elements(Action<ElementBuilder> builderAction)
    {
        ElementBuilder builder = new ElementBuilder(this);
        builderAction(builder);
        return this;
    }

    public class ElementBuilder
    {
        private readonly Config config;
        private readonly Element element;

        // Constructor called from Elements...
        internal ElementBuilder(Config config)
        {
            this.config = config;
            this.element = null;
        }

        // Constructor called from each method below
        internal ElementBuilder(Element element)
        {
            this.config = null;
            this.element = element;
        }

        public ElementBuilder Name(string name)
        {
            return Mutate(e => e.Name = name);
        }

        public ElementBuilder Height(int height)
        {
            return Mutate(e => e.Height = height);
        }

        // Convenience method to avoid repeating the logic for each
        // property-setting method
        private ElementBuilder Mutate(Action<Element> mutation)
        {
            // First mutation call: create a new element, return
            // a new builder containing it.
            if (element == null)
            {
                Element newElement = new Element();
                config.elements.Add(newElement);
                mutation(newElement);
                return new ElementBuilder(newElement);
            }
            // Subsequent mutation: just mutate the element, return
            // the existing builder
            mutation(element);
            return this;
        }
    }

    public class Element
    {
        public string Name { get; set; }
        public int Height { get; set; }
    }
}
于 2012-05-05T19:50:19.907 回答
3

我宁愿使用以下流畅的界面:

Config config = new Config("Foo")
                        .WithElement("element1", 23)
                        .WithElement("element2");

我认为它更具可读性和紧凑性。执行:

public class Config
{
    private string name;
    private IList<Element> elements = new List<Element>();

    public Config(string name)
    {
        this.name = name;
    }

    public Config WithElement(string name, int height = 0)
    {
        elements.Add(new Element() { Name = name, Height = height });
        return this;
    }

    class Element
    {
        public string Name { get; set; }
        public int Height { get; set; }
    }
}

如果 name 是可选的,则添加不带参数的 Config 构造函数。如果您不需要高度和名称,还可以考虑 WithElemnt 方法的可选参数。

更新:我将高度更改为可选参数,以显示如何添加仅指定名称的元素。

更新(如果您只想允许一组元素)

Config config = new List<Element>()
                    .AddElement(new Element {Name = "element1", Height = 23 })
                    .AddElement(new Element {Name = "element2" })
                    .WrapToConfig()
                    .Name("config1");

执行:

public static class ConfigurationHelper
{
    public static IList<Element> AddElement(this IList<Element> elements, Element element)
    {
        elements.Add(element);
        return elements;
    }

    public static Config WrapToConfig(this IList<Element> elements)
    {
        return Config(elements);
    }
}

但这对用户来说不是很明显,所以我会使用第一个简单流畅的界面。

于 2012-04-27T09:43:42.863 回答
2

您不想使用对象和集合初始化器的任何原因?

public class Config
{
   public string Name { get; set; }
   public int Foo { get; set; }
   public IList<Element> Elements { get; private set; }

   public Config()
   {
       Elements = new List<Element>();
   }
}

// I'm assuming an element *always* needs a name and a height
class Element
{
   public string Name { get; private set; }
   public int Height { get; private set; }

   public Element(string name, int height)
   {
       this.Name = name;
       this.Height = height;
   }
}

然后:

var config = new Config
{
    Name = "Foo",
    Elements = { 
        new Element("element1", 23),
        new Element("element2", 31)
    },
    Foo = 23
};

如果您不想直接公开元素列表,您可以随时将其转换为构建器,并将其复制到更私有的数据结构中Build

var config = new Config.Builder
{
    Name = "Foo",
    Elements = { 
        new Element("element1", 23),
        new Element("element2", 31)
    },
    Foo = 23
}.Build();

这具有额外的优势,您可以使Config自己不可变。

如果您总是需要Name在场,只需将其作为构造函数参数即可。

虽然有时拥有一个带有变异(或复制和更改)方法调用的流畅接口是件好事,但在这种情况下,我认为集合/对象初始值设定项更符合 C# 的习惯。

请注意,如果您使用 C# 4 并且想要Element调用构造函数,则始终可以使用命名参数:

new Element(name: "element2", height: 31)
于 2012-05-01T06:22:09.513 回答
1

使用数据构建器模式。这样做的好处是它将流畅的构建 api 与数据对象分开。当然,您可以在约定中省略“with”。

用法:

var aConfig = new ConfigBuilder();

// create config fluently with lambdas
Config config = aConfig.WithName("Foo")
        .WithElement(e => e.WithName("element1").WithHeight(23))
        .WithElement(e => e.WithName("element2").WithHeight(31))
    .WithFoo(3232)
    .Build();

// create elements in one go
config = aConfig.WithName("Foo")
         .WithElements(
             e => e.WithName("element1").WithHeight(23), 
             e => e.WithName("element2").WithHeight(31))
     .WithFoo(3232)
     .Build();


var anElement = new ElementBuilder();

// or with builders 
config = aConfig.WithName("Foo")
        .WithElement(anElement.WithName("element1").WithHeight(23))
        .WithElement(anElement.WithName("element2").WithHeight(31))
    .WithFoo(3232)
    .Build();

// use builders to reuse configuration code
anElement.WithHeigh(100);

config = aConfig.WithName("Bar")
        .WithElement(anElement.WithName("sameheight1"))
        .WithElement(anElement.WithName("sameheight2"))
    .WithFoo(5544)
    .Build();

执行:

public class ConfigBuilder
{
    private string name;
    private int foo;
    private List<Element> elements = new List<Element>();

    public ConfigBuilder WithName(string name)
    {
         this.name = name;
         return this;
    }

    public ConfigBuilder WithFoo(int foo)
    {
        this.foo = foo;
        return this;
    }

    public ConfigBuilder WithElement(Element element)
    {
        elements.Add(element);
        return this;
    }

    public ConfigBuilder WithElement(ElementBuilder element)
    {
        return WithElement(element.Build());
    }

    public ConfigBuilder WithElement(Action<ElementBuilder> builderConfig)
    {
         var elementBuilder = new ElementBuilder();
         builderConfig(elementBuilder);
         return this.WithElement(elementBuilder);
    }

    public ConfigBuilder WithElements(params Action<ElementBuilder>[] builderConfigs)
    {
         foreach(var config in builderConfigs)
         {
              this.WithElement(config);
         }

         return this;
    }

    public Config Build()
    {
         return new Config() 
         { 
             Name = this.name,
             Foo = this.foo,
             Elements = this.elements
         };
    }
}

public class ElementBuilder
{
    private string name;
    private int height;

    public ElementBuilder WithName(string name)
    {
        this.name = name;
        return this;
    }

    public ElementBuilder WithHeight(int height)
    {
        this.height = height;
        return this;
    }

    public Element Build()
    {
        return new Element() 
        { 
            Name = this.name,
            Height = this.height
        };
    }
}

public class Config
{
    public string Name { get; set; }
    public int Foo { get; set; }
    public IList<Element> Elements { get; set; }
}

public class Element
{
    public string Name { get; set; }
    public int Height { get; set; }
}
于 2012-04-30T14:06:26.173 回答
1

这是放置在 Config 中的方法 1—— “一次一个”:

public Config Element(Action<Element> a) {
    Element e = new Element();
    a(e);
    this.elements.Add(e);
    return this;
}

以下是如何使用它:

config.Name("Foo")
    .Element(e => e.Name("element1").Height(23))
    .Element(e => e.Name("element2").Height(31))
    .Foo(3232);

这是方法2 - “列表”:

public Config Elements(Func<List<Element>> a) {
    List<Element> elements = a();
    foreach (Element e in elements) {
        this.elements.Add(e);
    }
    return this;
}

以下是如何使用它:

config.Name("Foo")
    .Elements(() => new List<Element>() {
            new Element().Name("element1").Height(23),
            new Element().Name("element2").Height(31)
        })
    .Foo(3232);

请注意,它假定 Element 没有嵌套在 Config 中(或者您new Config.Element()在示例 2 中需要)。

请注意,在您的“列表”示例中,您传入了一个 Element 对象,但您尝试设置它两次。第二行将更改元素,而不是创建新元素。:

.Elements(e => {
                 e.Name("element1").Height(23); // <-- You set it
                 e.Name("element2").Height(31); // <-- You change it
              })
.Foo(3232);

因此这种语法不起作用。

这个怎么运作:

Func<T,U,...>是一个匿名函数委托,它接受除一个以外的所有参数,并返回最后一个。 Action<T,U,...>是一个匿名函数委托,它接受所有参数。例如:

Func<int,string> f = i => i.ToString();说“接受一个 int,返回一个字符串”。

Action<int> f = i => string c = i.ToString();说“接受一个 int,什么都不返回”。

于 2012-05-03T21:48:29.113 回答