在编写API或可重用对象时,为什么返回'void'的所有方法调用都不应该只返回'this'(*这在C ++中)有任何技术原因吗?
例如,使用字符串类,我们可以做这样的事情:
string input= ...;
string.Join(input.TrimStart().TrimEnd().Split("|"), "-");
但我们不能这样做:
string.Join(input.TrimStart().TrimEnd().Split("|").Reverse(), "-");
..因为Array.Reverse()返回void。
还有许多其他示例,其中API有许多返回void的操作,因此代码最终看起来像:
api.Method1();
api.Method2();
api.Method3();
..但完全有可能写下:
api.Method1().Method2().Method3()
..如果API设计师允许这样做。
是否存在遵循此路线的技术原因?或者它只是一种风格的东西,表示可变性/新对象?
(x-ref Stylistic question concerning returning void)
EPILOGUE
我接受了Luvieere的答案,因为我认为这最能代表意图/设计,但似乎有一些流行的API示例与此有所不同:
在C ++中,cout << setprecision(..) << number << setwidth(..) << othernumber;
似乎改变了cout对象,以便修改插入的下一个数据。
在.NET中,Stack.Pop()
和Queue.Dequeue()
都会返回一个项目,但也会更改集合。
向ChrisW和其他人道歉,详细了解实际的绩效成本。
答案 0 :(得分:26)
更清楚地返回void状态的方法,它们有副作用。返回修改结果的那些应该没有副作用,包括修改原始输入。使方法返回void意味着它改变了它的输入或API的其他内部状态。
答案 1 :(得分:19)
如果您Reverse()
返回string
,那么对于API的用户来说,它是否返回 new 字符串或同一个字符串并不明显,就地逆转。
string my_string = "hello";
string your_string = my_string.reverse(); // is my_string reversed or not?
这就是为什么,例如,在Python中,list.sort()
返回None
;它将就地排序与sorted(my_list)
区分开来。
答案 2 :(得分:5)
是否存在遵循此路线的技术原因?
C ++设计指南之一是“不要为不使用的功能付费”;返回this
会有一些(轻微的)性能损失,因为许多人(我,一个人)不会倾向于使用这个功能。
答案 3 :(得分:4)
许多其他人提到的技术主体(void
强调功能具有副作用的事实)被称为Command-Query Separation。
虽然这个原则有利有弊,例如(主观上)更清晰的意图与更简洁的API,但最重要的部分是保持一致。
答案 4 :(得分:4)
我想象一个原因可能是简单。很简单,API通常应该尽可能小。应该清楚它的每个方面,它是什么。
如果我看到一个返回void的函数,我知道返回类型并不重要。无论函数做什么,它都不会返回任何东西供我使用。
如果函数返回非void
的函数,我必须停下来并想知道为什么。这个对象可能会返回什么?为什么要归还?我可以假设this
总是返回,还是有时会为空?还是一个完全不同的对象?等等。
在第三方API中,如果不出现这类问题,我更愿意。
如果该功能不需要返回任何内容,则不应返回任何内容。
答案 5 :(得分:3)
如果您打算从F#调用您的API,请执行返回void
,除非您确信此特定方法调用几乎每次都会被链接到另一个使用
如果您不关心使用F#轻松使用API,可以在这里停止阅读。
在某些方面,F#比C#更严格 - 它希望你明确是否要调用方法来获取值,或者纯粹是因为它的副作用。因此,当该方法也返回值时调用其副作用的方法变得很笨拙,因为必须显式忽略返回的值以避免编译器错误。这使得“流畅的界面”在F#中使用起来有些尴尬,F#拥有自己的优良语法,可以将一系列调用链接在一起。
例如,假设我们有一个带Log
方法的日志系统,它返回this
以允许某种方法链接:
let add x y =
Logger.Log(String.Format("Adding {0} and {1}", x, y)) // #1
x + y // #2
在F#中,因为第1行是一个返回值的方法调用,并且我们没有对该值执行任何操作,所以add
函数被认为采用两个值并返回该Logger实例。但是,第2行不仅返回一个值,它出现在F#认为是该函数的“return”语句之后,实际上有两个“return”语句。这将导致编译器错误,我们需要显式忽略Log
方法的返回值以避免此错误,以便我们的add
方法只有一个return语句。
let add x y =
Logger.Log(String.Format("Adding {0} and {1}", x, y)) |> ignore
x + y
正如您可能猜到的那样,制作大量关于副作用的“Fluent API”调用在整个地方散布大量ignore
语句时会变得有些令人沮丧。
当然,您可以充分利用这两个方面,并为C#和F#开发人员提供流畅的API和F#模块,以便使用您的代码。但是,如果您不打算这样做,并且您打算将API用于公共消费,请在从每个方法返回this
之前三思而后行。
答案 6 :(得分:0)
除了设计原因外,还有一点性能成本(包括速度和空间)。