创建流畅的界面,以便将元素添加到列表中

Omu*_*Omu 4 .net c# fluent-interface

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

config.Name("Foo")
      .Elements(() => {
                         Element.Name("element1").Height(23);
                         Element.Name("element2").Height(31);
                      })
     .Foo(23);
Run Code Online (Sandbox Code Playgroud)

或者像这样:

  .Elements(e => {
                     e.Name("element1").Height(23);
                     e.Name("element2").Height(31);
                  })
  .Foo(3232);
Run Code Online (Sandbox Code Playgroud)

这就是我现在所拥有的:

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; }
   }
}
Run Code Online (Sandbox Code Playgroud)

谁知道怎么做?

Adr*_*ode 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);
      }
   }
}
Run Code Online (Sandbox Code Playgroud)

用法

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)
               }
    )
Run Code Online (Sandbox Code Playgroud)

  • 您还可以使用 params 关键字。 (2认同)

Jon*_*eet 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

笔记:

使用此代码,您必须先调用Name,然后可选地调用Height每个元素 - 但如果您无法调用,则不会抱怨任何内容Height.如果您将Elements呼叫更改为以下任一项:

  .Elements(e => {
                     e.NewElement().Name("element1").Height(23);
                     e.NewElement().Name("element2").Height(31);
                 })
Run Code Online (Sandbox Code Playgroud)

或这个:

  .Elements(e => {
                     e.Name("element1").Height(23).AddToConfig();
                     e.Name("element2").Height(31).AddToConfig();
                 })
Run Code Online (Sandbox Code Playgroud)

然后你最终会有一个更灵活的情况; 你可以拥有一个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; }
    }
}
Run Code Online (Sandbox Code Playgroud)


Jon*_*eet 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;
   }
}
Run Code Online (Sandbox Code Playgroud)

然后:

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

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

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

这还有一个额外的好处,就是你可以使其Config自身不可变。

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

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

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

new Element(name: "element2", height: 31)
Run Code Online (Sandbox Code Playgroud)