创建用于将元素添加到列表的流畅界面
本文关键字:界面 列表 用于 元素 添加 创建 | 更新日期: 2023-09-27 18:32:38
这就是我想要实现的目标:
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; }
}
}
有人知道如何做到这一点吗?
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)
}
)
这是一个完全按照第二个代码示例工作的版本。不过它真的很丑 - 我绝对不想自己使用它。最后的注释。
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; }
}
}
我宁愿使用以畅的界面:
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);
}
}
但这对用户来说不是很明显,所以我会选择第一个简单的流畅界面。
您不想使用对象和集合初始值设定项的任何原因?
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)
使用数据构建器模式。这样做的好处是它将流畅的构建 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; }
}
这是放置在 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 中(否则您需要在示例 2 中new Config.Element()
(。
请注意,在"列表"示例中,您已经传入了一个 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();
说"接受一个整数,返回一个字符串"。
Action<int> f = i => string c = i.ToString();
说"接受一个整数,什么也不返回"。