简体   繁体   中英

Why should an API return 'void'?

When writing an API or reusable object, is there any technical reason why all method calls that return 'void' shouldn't just return 'this' (*this in C++)?

For example, using the string class, we can do this kind of thing:

string input= ...;
string.Join(input.TrimStart().TrimEnd().Split("|"), "-");

but we can't do this:

string.Join(input.TrimStart().TrimEnd().Split("|").Reverse(), "-");

..because Array.Reverse() returns void.

There are many other examples where an API has lots of void-returning operations, so code ends up looking like:

api.Method1();
api.Method2();
api.Method3();

..but it would be perfectly possible to write:

api.Method1().Method2().Method3()

..if the API designer had allowed this.

Is there a technical reason for following this route? Or is it just a style thing, to indicate mutability/new object?

(x-ref Stylistic question concerning returning void )


EPILOGUE

I've accepted Luvieere's answer as I think this best represents the intention/design, but it seems there are popular API examples out there that deviate from this :

In C++ cout << setprecision(..) << number << setwidth(..) << othernumber; seems to alter the cout object in order to modify the next datum inserted.

In .NET, Stack.Pop() and Queue.Dequeue() both return an item but change the collection too.

Props to ChrisW and others for getting detailed on the actual performance costs.

Methods that return void state more clearly that they have side effects. The ones that return the modified result are supposed to have no side effects, including modifying the original input. Making a method return void implies that it changes its input or some other internal state of the API.

If you had Reverse() return a string , then it wouldn't be obvious to a user of the API whether it returned a new string or the same-one, reversed in-place.

string my_string = "hello";
string your_string = my_string.reverse(); // is my_string reversed or not?

That is why, for instance, in Python, list.sort() returns None ; it distinguishes the in-place sort from sorted(my_list) .

Is there a technical reason for following this route?

One of the C++ design guidelines is "don't pay for features you don't use"; returning this would have some (slight) performance penalty, for a feature which many people (I, for one) wouldn't be inclined to make use of.

The technical principal that many others have mentioned (that void emphasizes the fact the function has a side-effect) is known as Command-Query Separation .

While there are pros and cons to this principle, eg, (subjectively) clearer intent vs. more concise API, the most important part is to be consistent .

I'd imagine one reason might be simplicity. Quite simply, an API should generally be as minimal as possible. It should be clear with every aspect of it, what it is for.

If I see a function that returns void, I know that the return type is not important. Whatever the function does, it doesn't return anything for me to work with.

If a function returns something non- void , I have to stop and wonder why . What is this object that might be returned? Why is it returned? Can I assume that this is always returned, or will it sometimes be null? Or an entirely different object? And so on.

In a third-party API, I'd prefer if that kind of questions just never arise.

If the function doesn't need to return anything, it shouldn't return anything.

If you intend your API to be called from F#, please do return void unless you're convinced that this particular method call is going to be chained with another nearly every time it's used.

If you don't care about making your API easy to use from F#, you can stop reading here.

F# is more strict than C# in certain areas - it wants you to be explicit about whether you're calling a method in order to get a value, or purely for its side-effects. As a result, calling a method for its side-effects when that method also returns a value becomes awkward, because the returned value has to be explicitly ignored in order to avoid compiler errors. This makes "fluent interfaces" somewhat awkward to use from F#, which has its own superior syntax for chaining a series of calls together.

For example, suppose we have a logging system with a Log method that returns this to allow for some sort of method chaining:

let add x y =
    Logger.Log(String.Format("Adding {0} and {1}", x, y)) // #1
    x + y                                                 // #2

In F#, because line #1 is a method call that returns a value, and we're not doing anything with that value, the add function is considered to take two values and return that Logger instance. However, line #2 not only also returns a value, it appears after what F# considers to be the "return" statement for the function, effectively making two "return" statements. This will cause a compiler error, and we need to explicitly ignore the return value of the Log method to avoid this error, so that our add method has only a single return statement.

let add x y =
    Logger.Log(String.Format("Adding {0} and {1}", x, y)) |> ignore
    x + y

As you might guess, making lots of "Fluent API" calls that are mainly about side-effects becomes a somewhat frustrating exercise in sprinkling lots of ignore statements all over the place.

You can, of course, have the best of both worlds and please both C# and F# developers by providing both a fluent API and an F# module for working with your code. But if you're not going to do that, and you intend your API for public consumption, please think twice before returning this from every single method.

除了设计原因外,还有一点性能成本(包括速度和空间)。

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM