方法命名
只要明智地选择方法名称,流畅的接口就具有可读性。
考虑到这一点,我想将这个特定的 API 命名为“反流利的”:
System.Type.IsInstanceOfType
它是一个对象的成员System.Type
并接受一个对象,如果该对象是该类型的实例,则返回 true。不幸的是,您自然倾向于像这样从左到右阅读它:
o.IsInstanceOfType(t); // wrong
当它实际上是另一种方式时:
t.IsInstanceOfType(o); // right, but counter-intuitive
但并非所有方法都可能被命名(或定位在 BCL 中)以预测它们在“伪英语”代码中的出现方式,因此这并不是真正的批评。我只是指出流利接口的另一个方面——选择方法名称以减少意外。
对象初始化器
对于此处给出的许多示例,使用流畅接口的唯一原因是可以在单个表达式中初始化新分配的对象的多个属性。
但是 C# 有一个语言特性,经常使这种做法变得不必要 - 对象初始化器语法:
var myObj = new MyClass
{
SomeProperty = 5,
Another = true,
Complain = str => MessageBox.Show(str),
};
这也许可以解释为什么专业的 C# 用户不太熟悉用于在同一对象上链接调用的术语“流畅接口”——在 C# 中并不经常需要它。
由于属性可以具有手动编码的设置器,因此可以在新构造的对象上调用多个方法,而不必让每个方法返回相同的对象。
限制是:
- 属性设置器只能接受一个参数
- 属性设置器不能是通用的
如果我们可以在对象初始化程序块内调用方法和登记事件,以及分配给属性,我会很高兴。
var myObj = new MyClass
{
SomeProperty = 5,
Another = true,
Complain = str => MessageBox.Show(str),
DoSomething()
Click += (se, ev) => MessageBox.Show("Clicked!"),
};
为什么这样的修改块只能在施工后立即适用?我们可以有:
myObj with
{
SomeProperty = 5,
Another = true,
Complain = str => MessageBox.Show(str),
DoSomething(),
Click += (se, ev) => MessageBox.Show("Clicked!"),
}
这with
将是一个 new 关键字,它对某种类型的对象进行操作并产生相同的对象和类型 - 请注意,这将是一个表达式,而不是一个语句。所以它会准确地捕捉到在“流利的界面”中链接的想法。
因此,无论您是从new
表达式还是从 IOC 或工厂方法等获取对象,您都可以使用初始化程序样式的语法。
实际上,您可以with
在完成后使用new
它,它等效于对象初始化器的当前样式:
var myObj = new MyClass() with
{
SomeProperty = 5,
Another = true,
Complain = str => MessageBox.Show(str),
DoSomething(),
Click += (se, ev) => MessageBox.Show("Clicked!"),
};
正如查理在评论中指出的那样:
public static T With(this T with, Action<T> action)
{
if (with != null)
action(with);
return with;
}
上面的包装器只是强制一个不返回的动作返回一些东西,嘿,从这个意义上说,任何东西都可以是“流利的”。
等效于初始化程序,但具有事件登记:
var myObj = new MyClass().With(w =>
{
w.SomeProperty = 5;
w.Another = true;
w.Click += (se, ev) => MessageBox.Show("Clicked!");
};
在工厂方法而不是new
:
var myObj = Factory.Alloc().With(w =>
{
w.SomeProperty = 5;
w.Another = true;
w.Click += (se, ev) => MessageBox.Show("Clicked!");
};
我也忍不住给它“可能是单子”式的 null 检查,所以如果你有一些可能返回的东西null
,你仍然可以申请With
它,然后检查它的null
-ness。