return this
is not all there is to fluent interfaces.
Chaining methods is a simplistic form of building a fluent API, but fluent APIs generally look like DSLs (domain specific languages) and are much, much harder to design.
Take Moq as an example:
new Mock<IInterface>()
.Setup(x => x.Method())
.CallBack<IInterface>(Console.WriteLine)
.Returns(someValue);
The Setup
method, defined on the type Mock<T>
, returns an instance of ISetup<T, TResult>
.
The Callback
method, defined for ICallback<TMock, TResult>
returns an instance of IReturnsThrows<TMock,TResult>
. Note that ISetup<T, TResult>
extends IReturnsThrows<TMock,TResult>
.
Finally, Returns
is defined on IReturns<TMock,TResult>
and returns IReturnsResult<TMock>
. Also note that IReturnsThrows<TMock,TResult>
extends IReturnsResult<TMock>
.
All these little nuances are there to force you to call these methods in a particular order, and to forbid you from calling Setup
twice in a row, for example. Or from calling Returns
before you call Setup
.
These details are very important to ensure a good user experience.
To read more on designing fluent interfaces, take a look at Martin Fowler's article on FluentInterface. FluentAssertions is another prime example of how complex the design might get - but also of how much more readable the outcome is.