Method cascading operators are already used in languages such as Dart. Many examples here are taken from their documentation.
Wiki page: https://en.wikipedia.org/wiki/Method_cascading
Usually, fluent interfaces rely on method chaining. Say you want to add a large number of elements to a list:
``` C#
myTokenTable.Add("span");
myTokenTable.Add("div");
myTokenTable.Add("blockquote");
myTokenTable.Add("p");
myTokenTable.Add("small");
// ... many more here ...
myTokenTable.Add("blink");
You might want to write this as
``` C#
myTokenTable
.Add("span")
.Add("div")
.Add("blockquote")
.Add("p")
.Add("small")
// ... many more here ...
.Add("blink");
but this requires that Add() return the receiver, myTokenTable, instead of the element you just added. The API designer has to plan for this, it won't work for existing libraries, and it may conflict with other use cases. With cascades, no one needs to plan ahead or make this sort of tradeoff. The Add() method can do its usual thing and return its arguments. However, you can get a chaining effect using cascades:
``` C#
myTokenTable
..Add("span")
..Add("div")
..Add("blockquote")
..Add("p")
..Add("small")
// ... many more here ...
..Add("blink");
Here, ".." is the cascaded method invocation operation. The ".." syntax invokes a method (or setter or getter) but discards the result, and returns the original receiver instead.
Another example (**edit:** added parenthesis to eliminate ambiguity):
``` C#
String s = (new StringBuilder()
..Append('There are ')
..Append(beersCount)
..Append(' beers on the wall, ')
..Append(beersCount)
..Append(' number of beers...')
).ToString();
The success of frameworks like jQuery show that method call chaining is easy to learn, intuitive and makes for easier to read code.
If we had a struct TimeMarker { DateTime Time; event Action Moved; }
class, and wanted to create a modified clone of an existing marker, we would have to do something like:
``` C#
TimeMarker createMarkerAhead(TimeMarker marker, TimeSpan ahead, Action movedAction) {
var aheadMarker = new TimeMarker(marker);
aheadMarker.Time += ahead;
aheadMarker.Moved += movedAction;
return aheadMarker;
}
Which is quite long. We could do it in one line using the object initializer syntax:
``` C#
TimeMarker createMarkerAhead(TimeMarker marker, TimeSpan ahead, Action movedAction) {
var aheadMarker = new TimeMarker(marker) { Time = marker.Time + ahead };
aheadMarker.Moved += movedAction;
return aheadMarker;
}
But this sets the Time
variable twice, and doesn't really make it any easier to read. The method cascading operators would allow chaining calls:
``` C#
TimeMarker createMarkerAhead(TimeMarker marker, TimeSpan ahead, Action movedAction) {
return new TimeMarker(marker)
..Time += ahead
..Moved += movedAction;
}
This seems especially useful for creating form components and assigning event listeners at the spot of creation, instead of on the lines after.
Companion operators `?..` and `?..[]` could also be added for safe access:
``` C#
var topStudent = topStudents.FirstOrDefault(student => student.Grade >= 8.5)
?..Grant += 1000;
watchedEpisodes
?..[1] = true
?..[4] = true
?..[7] = true;
Even more examples of use cases here: http://news.dartlang.org/2012/02/method-cascades-in-dart-posted-by-gilad.html
Your StringBuilder
example is flawed, as the .Append()
and .AppendLine()
methods already return the StringBuilder instance to provide a fluent API.
Other than that, I can certainly see the value in this proposal, and it shouldn't be significantly hard to implement I think, since the compiler could emit the same kind of calls as for an Object Initializer.
I often write my APIs to follow this kind of convention. However, baking it into the language so that it happens to work for types where it has not been explicitly designed I think is a bad idea. For example, the following would not return what the user expected:
string foo = "Hello World!";
string bar = foo.Replace("Hello", "Goodbye")
..Replace("World", "Nurse");
Debug.Assert(bar == "Goodbye Nurse!"); // nope, it's "Hello Nurse!"
The compiler could warn/fail if the method being chained returns something other than void
. It could potentially do the same if the method returns a different type, however I would be concerned about situations where you start with a concrete type and the methods return interfaces that represent the affected values. I don't think that cascades fit into the currently push towards more immutability.
As for how you're attempting to chain property assignments or event subscriptions, that I can't see working due to the whitespace in the assignment. What exactly would foo.bar = baz.fizz..buzz();
do? That syntax just looks unattractive and unintuitive.
Is it particularly cumbersome to write a fluent API in C# right now? If a type is useful with one, then it likely already has such methods or you could easily make your own extension methods. JQuery is a poor example- JavaScript has no such ..
feature- it's written the old fashioned way; by returning this
after each call.
To me this seems like a solution in search of a problem.
Aren't you really proposing another form of with
operator?
@MgSam
Is it particularly cumbersome to write a fluent API in C# right now?
Yes. And trying to add features around it (like you normally would do via dynamic dispatch and other OOP mechanisms) can be much more difficult than a non-fluent version of the API. Because of this, I now regret utilizing a fairly popular fluent C# library over a non-fluent one.
But I don't know that small language improvements will make it substantially less cumbersome. Fluent APIs are a symptom that C# doesn't support embedded DSLs to the degree that is needed. If we want to embed DSLs, we should be able to _really embed_ them with full compiler and tools support. Something like this, which I also mentioned here:
c#
var toc = outline {
Table of Contents
1. Before you begin
2. Introduction
3. Compiler concepts
a. Lexer
b. Parser
c. Checker
d. Emitter
4. Creating your own language
a. Design
b. Optimization
c. Tooling
};
where outline
invokes some custom DSL toolchain on the supplied block.
_Head meets the desk._
_^LOLzed about comment above_
I think, that the Proposal is wonderful, but it is in my view and extended with
-operator (which C# is of course missing)...
I would -however- like to see an other operator token than ..
-- Maybe some sort of pipe? Or other symbols/tokens, which are (not yet) so much in use, like 搂
, 炉
, \
, ::
, etc.
Another real-life example I stumbled upon today:
``` C#
Thread StartBackgroundThread(ThreadStart threadStart, string name = null)
{
var thread = new Thread(threadStart) { Name = name, IsBackground = true };
thread.Start();
return thread;
}
And with `..` operator:
``` C#
Thread StartBackgroundThread(ThreadStart threadStart, string name = null)
{
return new Thread(threadStart) { Name = name, IsBackground = true }
..Start();
}
HaloFour pointed out a need for clarification for when the ..
operator is used without whitespace. For example, let's consider the following expression:
C#
var result = Foo.MakeAFoo()..Bar="Hello"..Baz=1234.ToString();
First, the ..
operator should not be nestable, so multiple usages of it will always apply to the first expression.
Second, C# is not whitespace insensitive. For example 1234.ToString()
is a valid expression, while the expression 1234 .ToString()
is not (notice the space before the dot). So the presence or absence of whitespace between 1234
and .ToString()
determines whether it applies to 1234
or the root expression.
Edit: Seems I was wrong about the whitespace. Thanks @HaloFour.
@manixrock
Replying here since the other issue is closed.
The
..
operator should not be nestable so there's only even one level of it, so any subsequent usages of..
will apply to the same expression as the previous one, eliminating any ambiguity.
What defines a "level"? The top-most expression? If you couldn't use this operator within an existing expression that would kill much of the use of it. Otherwise this seems like just a very hobbled form of with
.
For example
1234.ToString()
is a valid expression, while1234 .ToString()
is not (notice the space before the dot).
Incorrect. The following is perfectly legal C#, although the formatter in VS will fight you.
string s1 = 1234.ToString();
string s2 = 1234. ToString();
string s3 = 1234 .ToString();
string s4 = 1234 . ToString();
string s5 = 1234
.ToString();
string s6 = 1234.
ToString();
string s7 = 1234
.
ToString();
@HeloFour by "the first expression" I mean the first expression on which the first ..
is used.
Regarding the whitespace issue I was incorrect. However we can use curly braces to cover both use cases:
``` C#
// .Baz is assigned "1234", result is Foo.MakeAFoo()
var result = Foo.MakeAFoo()..Bar="Hello"..Baz=1234.ToString();
// .Baz is assigned 1234, result is Foo.MakeAFoo().ToString()
var result = Foo.MakeAFoo(){..Bar="Hello"..Baz=1234}.ToString();
// same as above, maybe a bit more readable
var result = Foo.MakeAFoo(){ ..Bar = "Hello" }{ ..Baz = 1234 }.ToString();
// same as above, maybe a bit more readable
var result = Foo.MakeAFoo(){ ..Bar = "Hello", ..Baz = 1234 }.ToString();
It even looks a bit like the object initializer.
The operator is usable with any existing expression.
``` C#
var form = (someCondition ? form1 : form2)
..Text = "Best Form"
..TopLabel.Text = "Best Label";
I wonder if the operator could also be used to create shorter lambdas. For example ..Bar
without any assignment would translate to a => a.Bar
.
This could be used for easier to read Linq queries:
``` C#
var topStudentNames = students
.OrderByDescending(student => student.Grade)
.Take(5)
.Select(student => student.Name);
Would become:
``` C#
var topStudentNames = students
.OrderByDescending(..Grade)
.Take(5)
.Select(..Name);
And it could work equally well with the null-checking version:
``` C#
var dogOwners = dogs.Select(?..Owner);
Alternatively, all `..` expressions would return a Func if we introduce single-argument `{}`-delimited lambdas with `.Abc` returning the field `Abc` of the only argument.
``` C#
Func<Dog, Person> ownerGetter = ..Owner;
Func<Dog, Dog> winnerMarker = { ..IsWinningDog = true };
Func<Dog, bool> ageCheck = { .Age > 7 };
// expanded versions
Func<Dog, Person> ownerGetter = dog => dog.Owner;
Func<Dog, Dog> winnerMarker = dog => { dog.IsWinningDog = true; return dog; };
Func<Dog, bool> ageCheck = dog => { return dog.Age > 7; };
// when appended to an expression they run and return the original value
var dog1 = getWinningDog()..IsWinningDog = true;
var dog2 = getWinningDog() { ..IsWinningDog = true }; // same
var dog3 = getWinningDog() { .IsWinningDog = true }; // same
var dog4 = getWinningDog() { winnerMarker }; // same
This would also probably work nicely with immutable structs allowing for short copy-and-change expressions.
I accidentally closed this issue a week ago by mistake. I reopened it soon after, but I haven't seen any comments since. How can I check that the issue is indeed open?
It shows Open at the top.
@bondsbw Got it, thanks.
This example of an unexpected result provided by @HaloFour
``` C#
string foo = "Hello World!";
string bar = foo.Replace("Hello", "Goodbye")
..Replace("World", "Nurse");
Debug.Assert(bar == "Goodbye Nurse!"); // nope, it's "Hello Nurse!"
```
Pretty much makes this a nonstarter for me. Is anyone _not_ going to be confused by this? Imagine the interactions with LINQ where an extra .
was added by accident when someone was breaking a method chain across multiple lines...
This proposal trades little value for a pretty hefty cost; readability actually suffers in most of the examples I've seen. For instance:
var aheadMarker = new TimeMarker(marker) { Time = marker.Time + ahead };
aheadMarker.Moved += movedAction;
return aheadMarker;
vs.
return new TimeMarker(marker)
..Time += ahead
..Moved += movedAction;
Sure the second one is smaller, but I'm forced to stare a bit harder at it to comprehend what's happening. Readability trumps writability every time.
My preference would be something like a 'with' statement; it more clearly signals what is going on. But, in either case, I'm not sure there's enough evidence to suggest there'd be a significant improvement in readability. In other words I'd rather declare a variable and use it repeatedly as the LHS of method invocations. It's not much more verbose and it's well understood and clearly readable.
var am = new TimeMarker(marker);
am.Time += ahead
am.Moved += movedAction;
return am;
That's not so bad, right?
@rsmckinney I can understand your concern, but I actually find the proposal more readable.
I would suggest some sort of pipe-symol instead of ..
, as this would add more intuitiveness (but it should not be confused with function-piping).
I think the with
-keyword would be the best alternative.....
With language design, smaller is notoriously mistaken for more concise. In my view this feature falls into that category. Either way my point here is there doesn't appear to be enough value from this operator to justify adding it to the language. First, I'm not convinced the frequency of use is there. How often would the average programmer use this? My guess is pretty infrequently, probably on the order of switch statements, thus a strong candidate for stackoverflow hits especially for readers of this syntax. Above all, in my view the code is less readable, others may disagree, so let's say there is no gain or loss in readability, a lateral change. So why complicate the language with another member access operator? There's no performance gain, no readability improvement, just a tad smaller code to write. The math is not in favor.
All this said, it's still an interesting idea!
a strong candidate for stackoverflow hits
A good point. Ever tried searching for ..
on SO, Google, or Bing?
Dart lang implemented the cascading operator identically to this proposal.
I could only find one related question in StackOverflow, which suggests it is not that confusing after all.
With language design, smaller is notoriously mistaken for more concise.
You could ask the same for constructor initialization syntax...
I'm in favor of this suggestion - it's simple, useful & elegant.
Language design issues are now in csharplang. I'll go ahead and close.
https://github.com/dotnet/csharplang/issues/781 seems to be the most related.
Most helpful comment
I often write my APIs to follow this kind of convention. However, baking it into the language so that it happens to work for types where it has not been explicitly designed I think is a bad idea. For example, the following would not return what the user expected:
The compiler could warn/fail if the method being chained returns something other than
void
. It could potentially do the same if the method returns a different type, however I would be concerned about situations where you start with a concrete type and the methods return interfaces that represent the affected values. I don't think that cascades fit into the currently push towards more immutability.As for how you're attempting to chain property assignments or event subscriptions, that I can't see working due to the whitespace in the assignment. What exactly would
foo.bar = baz.fizz..buzz();
do? That syntax just looks unattractive and unintuitive.