Fluent API support for static methods #7847
Unanswered
AlexRadch
asked this question in
Language Ideas
Replies: 1 comment 6 replies
-
I could see this being able to be done with a Roslyn Source Generator, by creating ( ideally a ref struct) that has methods with identical signatures that's just a wrapper around the original one. [Fluent]
public static class Guard
{
public static void NotNull(object o) {...}
public static void NotFull(object o) {...}
} The previous code spits out something like this public ref struct FluentGuard
{
public FluentGuard NotEmpty(object o)
{
Guard.NotEmpty(o)
return this;
}
public FluentGuard NotFull(object o)
{
Guard.NotFull(o);
}
} And you'd call it like this new FluentGuard().NotEmpty(myObj).NotFull(); I'm not saying this is pretty - and this particular psycho code is probably wrong in many ways - but the concept might work.. or maybe not 🫠 |
Beta Was this translation helpful? Give feedback.
6 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Motivation
The fluent API is one of the most commonly used design patterns in C#. It is elegant and expressive, so a fluent code reads like prose. It is widely and ubiquitously used in libraries and public APIs. However, creating a fluent API can require a lot of ceremony. It is especially difficult to create a fluent API for static methods.
The idea of how to extend C# to create a Fluent API for static methods came about when I was working on extending the Guard class from the .NET Community Toolkit library. The Guard class can be used to validate method arguments in a streamlined manner, which is also faster, less verbose, more expressive, and less error-prone than manually writing checks and throwing exceptions.
The
Guard
has many static methods and does not support fluent API style and he didn’t need to support fluent API style until primary constructors were added to C#. There was a desire to support parameter checking in primary constructors.As you can see in the code above, the
Guard
class must support a fluent API style for checking the parameters in primary constructors.How can we add support for primary constructors to the
Guard
class? We can't convert theGuard
class methods into functions for several reasons. Firstly, the class will become binary incompatible with earlier versions, and secondly, a compiler warning will appear stating that the return value is not used anywhere in old classic methods.One solution would be to add a
FGuard
(fluent guard) class whose extension functions would return a checked parameter. At the moment this is the best solution that C# can offer us. This solution has a huge disadvantage: We would have to duplicate a large number of methods of the original class, duplicate test methods for them, duplicate documentation for them, and then maintain it all in double size, which of course we want to avoid. Adding extension functions to theFGuard
class begins to extend the available methods for all classes, which is also not very convenient. The extension functions of theFGuard
class are needed only by primary constructors, but they become available throughout the program.So I came up with the following objectives.
Objectives
To reduce the number of ceremonies, it is necessary to implement a fluent API for existing static methods:
The result should look like we have added extension functions to the original class and also satisfy the constraints mentioned in 4-5 points above.
Suggestions
I suggest adding the [Fluent] attribute to the parameter of the method for which we are adding support for the Fluent API. This is what it would look like for the original
Guard
class:As you can see, we did not change anything in the
Guard
class, we only added the[Fluent]
attribute to its parameter, its binary compatibility has not changed.To the compiler, this attribute will mean two things:
[Fluent]
attribute will be automatically used to call the next method as if this method would return this parameter unchanged.[Fluent]
attribute become extension methods only for the parameter returned in the first point. Class methods do not become extension methods throughout the application because we did not add thethis
modifier to a parameter.Below I showed how the compiler will compile code with the new fluent API.
Thus, the
[Fluent]
attribute allows the compiler to add full support for the new Fluent API to theGuard
class without having to write a lot of duplicate code and documentation.Likewise, this feature will be useful for adding Fluent API to many existing classes.
Other discussions on this topic
Fluent API and builders: language-level support
Return this type for fluent API
Discussion: implicit return of this for builder pattern with this type
"this" type
Implicit this return for Builder Pattern
Performance-effective Fluent API support
Beta Was this translation helpful? Give feedback.
All reactions