𝄯 Sharp Regrets: Top 10 Worst C# Features
-
But pre does the exact same thing as +=1. Furthermore, modifying a variable using an operator located on the left side of the variable runs counter to every other feature in the entire language. It's completely unjustified.
-
Agreed. IComparable should automatically give you all the operators (the equality and inequality operators should belong to IEqualable, which IComparable should implement).
Couldn't agree more.
If only I could define extensions for the comparison operators, it wouldn't be such a PITA to have to define six operators for every single class that I want to have an order. (Maybe I could, but I didn't dig deep enough into the Reflection so far.)
-
But pre does the exact same thing as +=1. Furthermore, modifying a variable using an operator located on the left side of the variable runs counter to every other feature in the entire language. It's completely unjustified.
Except for incrementing / decrementing something by one is a so frequent operation, that IMNSHO opinion it makes quite sense to see at a glance where that is done. (Alternative would be adding forcount loops.)
Edit: afterthought: The post-increments could be all replaced by pre-increments in for loops, of course. I think the reason for introducing the post-increment operator into C originally was to save a few microseconds back when processor clock frequencies were in the single-digit MHz range.
-
And there is a perfectly good ++ operator for that, which belongs on the right hand side of the variable.
-
belongs on the right hand side of the variable.
Matter of taste.
Unless you actually want to play code golf and/or have to program very tight loops which IMHO is the only case where the trade-off in readability is justified. (If not too many levels nested, this is a matter of taste, too, I admit.)
-
-
Regarding for, in 99.9% of the cases where you are using the three-clause version you really should be using an iterator or a while loop.
One thing I like about the classic for loop syntax is that the important bits about the loop are all right there. It's easier to mess that up with an equivalent while loop. Not that I don't use them when appropriate.
-
One thing I like about the classic for loop syntax is that the important bits about the loop are all right there. It's easier to mess that up with an equivalent while loop. Not that I don't use them when appropriate.
Also, a
continue
will execute the step clause. With awhile
, you may have to put copies of the step clause in several locations.
-
One thing I like about the classic for loop syntax is that the important bits about the loop are all right there
This, I actually prefer old style for() loop syntax. I don't really understand the hate for it. Is there a performance penalty from using it with modern compilers and interpreters?
-
With a while, you may have to put copies of the step clause in several locations.
I'd suggest to add something like a "finally" to while and similar loops, a block that is executed every time a continue or an exit is executed. (Well, we'd have to have two or three different blocks for that, so maybe we rather shouldn't.)
-
A
finally
would be one-shot unless you put it inside the loop.
-
@boomzilla said:
One thing I like about the classic for loop syntax is that the important bits about the loop are all right there. It's easier to mess that up with an equivalent while loop. Not that I don't use them when appropriate.
Also, a
continue
will execute the step clause. With awhile
, you may have to put copies of the step clause in several locations.Yep. Stuff like that is exactly the sort of thing I was thinking about.
-
Is there a performance penalty from using it with modern compilers and interpreters?
Not in the slightest. Nor is there a performance bonus. Modern compilers atomise your code down to basic blocks, however you provide it.
-
I'd suggest to add something like a "finally" to while and similar loops, a block that is executed every time a continue or an exit is executed. (Well, we'd have to have two or three different blocks for that, so maybe we rather shouldn't.)
The biggest reason I use
while
loops is when I need to use different ways to get to the next step. Most commonly, it's when I want to filter a list. So I either remove the element or increment the index. Can be done with afor
loop, but of course you need to decrement the index after removing the item. In this case, I find thewhile
loop cleaner, because the iteration options are still close together, typically separated by anelse
.
-
That's what I thought. Is there a readability penalty?
-
The biggest reason I use while loops is when I need to use different ways to get to the next step. Most commonly, it's when I want to filter a list. So I either remove the element or increment the index. Can be done with a for loop, but of course you need to decrement the index after removing the item. In this case, I find the while loop cleaner, because the iteration options are still close together, typically separated by an else.
Same here.
But why shouldn't we add something to a language to make it a bit less clear if there are 0.01% of legitimate usages for it, if we've done so thousands of times before?
-
In ideal world, FFI is not a problem, so you can mix languages freely without problem.
facepalm Even I can see the problem with that argument, and I'm about as ivory-tower as they come.
-
@Gaska said:
In ideal world, FFI is not a problem, so you can mix languages freely without problem.
facepalm Even I can see the problem with that argument, and I'm about as ivory-tower as they come.
In an ideal world, there would be only one language that enables everyone to be Doing It Right™.
-
In an ideal world, there would be only one language that enables everyone to be Doing It Right™.
In an ideal world, there's no such thing as programming and all immortal human beings live in a permanent state of everlasting ecstasy.
-
In an ideal world, all would agree about the definition of "ideal world".
-
-
Is there a readability penalty?
There's a readability penalty to having the
step
clause repeated in multiple locations, especially as it can be unclear that it really is astep
.
-
It would probably end up a bit nasty in C# and Java due to the type hierarchy having a root in o/Object.
Not really.
void
cast toobject
would result innull
, which is the more dynamic variant of “nothing to see here” and which both those languages unfortunately have.You'd be OK in Java, as you could make it be a non-reference type and they live outside the hierarchy. No idea about C#, though I think that tries to be more consistent and that causes issues for this…
Non-reference objects are auto-boxed in both. Anyway,
null
is fine asobject
representation ofvoid
.I wonder if you can overload in class extensions?
C++ does not have class extensions. It can, however, overload most operators with free functions, which has approximately the same effect. In fact that is the reason C++ standard library abuses operators so much. E.g. the use of
<<
for output is clearly abuse. But when boost comes around with saner formatting, it abuses%
, because functions, member or non-member, didn't provide enough flexibility in C++03 (C++11 introduces variadic templates that can provide enough flexibility and saner syntax, but AFAICT the proposal for new formatting function did not get anywhere yet).I mean seriously, I don't get all this hate for operators.
I don't get it either and I didn't say a word against them. Because I believe operator overloading is essential ability of any serious language that features operators (obviously L*sp does not need them). I was just saying that good implementation of operator overloading should be able to fill in the related operators so you don't have to repeat yourself.
And even if you say there are some types that the compiler can't know are pointers, you can't access much from -> in those anyway until you cast it to another type.
C++ has types that are not pointers, but behave like ones. And for these you want a dereference-and-get-member shortcut.
Note, that in C++
->
is overloadable while.
is not.Long ago when I started programming there was still Pascal and it was used in classes. I didn't like it much for various reasons, but what I did like was that suffix
^
was dereference operator, which naturally composed to^.
meaning what->
does in C++ without needing special operator. However since then all languages stick with prefix dereference operator and need a special dereference-and-get-member operator (or magic like Rust) to avoid parenthesis.Why do you expect flags to work like integers?
I personally expect flags to work like sets. For sets the correct operators are
∪
and∩
. Traditionally|
was used for the former and&
for the later. Using+
for the former and*
for the later would make some kind of sense, but I prefer the|
and&
.My opinion is that removing the redundant pre-increment operator
It is post-increment that is redundant. It is the odd man out here; all other operations return their result, only post in/decrement return their input.
Agreed. IComparable should automatically give you all the operators (the equality and inequality operators should belong to IEqualable, which IComparable should implement).
Is it possible to overload operators in extension classes? If not, it should be and then this could be fixed.
I think the reason for introducing the post-increment operator into C originally was to save a few microseconds back when processor clock frequencies were in the single-digit MHz range.
Post-increment was never more efficient than pre-increment, because it had to stash the value-to-be-returned somewhere. Which was the case before compilers became able to optimize it away when needed and which is the case when the operator is overloaded (because the function returns the value even if it is not needed anyway).
I suspect the reason why post-increment was added was to make this cute implementation of strcpy possible:
while(*d++ = *s++);
And there is a perfectly good ++ operator for that, which belongs on the right hand side of the variable.
As explained above, most of the time it belongs on the left side.
tight loops which IMHO is the only case where the trade-off in readability is justified
It is where the trade-off in readability is particularly not justified, because more readable code is generally more readable for the optimizer as well!
-
I personally expect flags to work like sets. For sets the correct operators are ∪ and ∩. Traditionally | was used for the former and & for the later. Using + for the former and * for the later would make some kind of sense, but I prefer the | and &.
Fair point. Forgot about those.Is it possible to overload operators in extension classes? If not, it should be and then this could be fixed.
The reason why extension classes can't overload operators is probably to avoid conflicts - for example, when you have two libraries that overload operator for the same type.
-
The reason why extension classes can't overload operators is probably to avoid conflicts - for example, when you have two libraries that overload operator for the same type.
You have to bring the namespace containing the extension into scope for the extension to be used. So if two libraries overloaded operator for the same type, you'd just have to avoid importing both in the same compilation unit.
-
Post-increment was never more efficient than pre-increment, because it had to stash the value-to-be-returned somewhere.
I'm not so sure. In case of one-word integers, a post-increment could be implemented like this:
push A incr A
(the function result had to be put on the stack anyway, so no time loss.)
(Not that I'd be able to think of any sensible usage of this right now.)
I suspect the reason why post-increment was added was to make this cute implementation of strcpy possible:
while(*d++ = *s++);
That would be an essential benefit in the eyes of the notoriously keyboard lazy hackers of that time.more readable code is generally more readable for the optimizer as well!
If there is an optimizer at all, wich IIRC wasn't the case in the toddler days of C.
From today's point of view, I agree with all of your post, of course.
-
In case of one-word integers, a post-increment could be implemented like this
It only matters when you're using the result of the operation. If you're not using it, the compiler should (usually) be able to optimise it all out. Mind you, for C and Java this is all an oddity of not much consequence. It's for C++ and C# that it matters because they allow operator overloading (whence the “fun” comes).
-
That's what I thought. Is there a readability penalty?
Yes, it has three clauses and it's not readily apparent which clause is which. Of course if you have a lot of experience with it, it seems perfectly clear, but that doesn't make it a good design.
-
Why do you expect flags to work like integers?
I don't
But if we're capable of understanding that
value += another flag
doesn't really mean adding integers, then we can understand thatevent += subscriber
is equally understandable.and A + B shouldn't modify its arguments.
++A modifies its own argument.
I mean, I don't get the confusion here.
Add subscriber to event's list of subscribers.
There's no other operator that makes sense, given that -= removes the subscriber.
And I'd much rather use an operator.
C++ has types that are not pointers, but behave like ones. And for these you want a dereference-and-get-member shortcut.
That.... may be...
Well, if it's the case, then it's the case.I suspect the reason why post-increment was added was to make this cute implementation of strcpy possible:
Or any other loop really, where you put the pointer at the first index and let post-increment iterate for you.
-
It still requires you to overload each operator separately though.
I was talking about value vs referential equality, not all comparison operators (which are only related to the value semantics). I agree that it would be convenient if the language itself did what the standard library algorithms do, which is use the "less-than" operator to get every other comparison right.
You could create a set of template comparison operators, once, and then get every comparison operator done by just providing the less than operator, however. Optionally, provide the equality operator as a performance improvement (as implementing it in terms of the less than comparison could require two comparisons).
// user provided operator < bool operator<(MyClass const& lhs, MyClass const& rhs); template <class T> bool operator>(T const& lhs, T const& rhs) { return rhs<lhs; } template <class T> bool operator>=(T const& lhs, T const& rhs) { return !lhs<rhs; } template <class T> bool operator<=(T const& lhs, T const& rhs) { return !lhs>rhs; } // optionally, provide your own equality if it would be faster than this template <class T> bool operator==(T const& lhs, T const& rhs) { return !lhs<rhs && !lhs>rhs; } template <class T> bool operator!=(T const& lhs, T const& rhs) { return !lhs==rhs; }
There, I fixed the language. Now every type for which you provide the less than operator gets every other operator free, and it's all consistent.
From a puritan standpoint, maaaaybe, though it's not as much "implementation detail" as "common knowledge". But that's how literally every language in the world works, people are used to numbers being twiddlable, and on the bottom line you're still ANDing two integers on the processor - but now you need to convert into them from bit arrays and vice versa. And those bit arrays pretty necessarily take at least 1 byte per bit, or more - I know memory and cycles are cheap, but come on, that's just a waste of power.
You don't need to implement an "array of bits" with one byte per bit. That's implementation specific stuff. In fact, counter-intuitively, in C++ the std::vector<\bool>, unlike every other vector, is specialized so that it doesn't use a distinct memory address for each element but instead collapses the bools into packed bits.Similarly, a "flag" type would have an interface that is implemented with integers and bit fiddling, but the user is not exposed to that. The user just knows that flags are things that can be combined, and enums are things that are distinct. That you can implement one in terms of the other becomes something you don't need to concern yourself with, and thus can't mess up.
The fact that it's common knowledge is what makes people mess it up. They think they understand it and don't pay attention. If people didn't mess it up, it wouldn't ever come up.
That.... may be...Well, if it's the case, then it's the case.
It's how "smart pointers" are implemented. That's a class that wraps a pointer, and automatically cleans up the memory when it runs out of scope (or decreases a ref-count in the case of shared_ptr and frees the memory when the ref count reaches 0). It allows the smart-pointers to essentially have the same syntax as the raw pointers.
-
That's great, but neither of you clowns is Mr. Gamergate Xaaaaaaaaaaaade.
Um, actually, can you PROVE that "fact", or are you just parroting what some feminist told you?
-
There, I fixed the language.
No, you broke it pretty badly. It takes a lot of SFINAE magic to do it correctly.
-
Is there any person doing programming who doesn't have experience with C-style syntax? Even if they don't use it in any way, I feel like it really isn't hard to parse.
For([Set this up before the loop runs]; [Keep looping while this is still true]; [Do this after every run through the loop])
I don't see what's so hard about this
-
This post is deleted!
-
But that's how literally every language in the world works, people are used to numbers being twiddlable,
Well, ok, but C# is supposed to be strongly-typed.
If you treat a bitfield and a integer as interchangeable, based on some low-level unspoken assumption of how the bitfield is implemented, C# is no longer strongly-typed, it's wish-it-were-strongly-typed.
(Sadly, that's true now, because of various reasons, but that's no excuse to make it worse.)
I would also like to mention, "it's always been done that way" is a really shitty reason for making any decision.
-
One thing I like about the classic for loop syntax is that the important bits about the loop are all right there. It's easier to mess that up with an equivalent while loop. Not that I don't use them when appropriate.
Nobody's comparing
for(;;)
towhile()
, we're comparing it toforeach()
.The addition of LINQ basically changed a lot about how C# is supposed to be working, and a lot of people haven't caught up to that yet. I think that's the basic conflict here. In .NET 1.1, sure, use your C-like
for(;;)
loops, because that's really the best thing you got in a lot of situations. In LINQ-ville, that's all obsolete.
-
Most commonly, it's when I want to filter a list.
You do that in LINQ now. It's much, much, much easier to write. (And potentially more performant, since LINQ in a lot of situations can get away with not bothering to enumerate.)
Again, this is .NET 1.1 thinking.
-
Nobody's comparing for(;;) to while(), we're comparing it to foreach().
Keep up with the discussion, please.
Anyways, you can't change the list if you're using a foreach construct.
You do that in LINQ now.
I probably would if I used C#, which I don't.
Filed Under:Shamalama plot twist!
-
Is there any person doing programming who doesn't have experience with C-style syntax? Even if they don't use it in any way, I feel like it really isn't hard to parse.
For([Set this up before the loop runs]; [Keep looping while this is still true]; [Do this after every run through the loop])
I don't see what's so hard about this
It's not that it's hard, it's that it's non-intuitive. It steepens the learning curve and makes errors hard to notice at a glance.
As a similar example: the most common error that people at my office make is accidentally using = where they should have used ==. Everyone knows the right syntax, but when they venture into territory where a compiler can't give them warnings (an Angular template for example), these bugs creep in. A better language would be one where = can't be used in an expression context.
-
Keep up with the discussion, please.
Then what the fuck were you talking about?
Anyways, you can't change the list if you're using a foreach construct.
No; you use LINQ and
.Remove()
. (Or whatever LINQ operation is most relevant to what you're doing.)I probably would if I used C#, which I don't.
Then why the fuck are you posting in here?
-
Anyways, you can't change the list if you're using a foreach construct.
That's where I used to end up iterating over the index instead of the list itself. The alternative was to iterate the list, collect a list of items to remove, and remove them at the end. However, since LINQ was invented, it's easier to pare the list down with a lambda expression rather than a loop.
-
Then why the fuck are you posting in here?
I was discussing stuff with some of the adults in the topic. Exchanging ideas can be intellectually stimulating and enjoyable. I highly recommend it.
-
I was discussing stuff with some of the adults in the topic.
Is that why you were telling me, "keep up" because you completely changed the topic that says C# right there in big bold letters at the top of the screen to be about something other than C#?
And obviously it's my fault that my telepathic powers didn't kick-in and inform me that despite saying C# in big bold letters at the top of the screen, this conversation is no longer about C#?
I hate you.
-
Is that why you were telling me, "keep up" because you completely changed the topic that says C# right there in big bold letters at the top of the screen to be about something other than C#?
The topic changed in that conversation a while before that.
And obviously it's my fault that my telepathic powers didn't kick-in and inform me that despite saying C# in big bold letters at the top of the screen, this conversation is no longer about C#?
No, you just suck at reading, as usual. Are you confused by how some people don't regret certain things about C#, too? What if their regrets are rather dull? Do you need an annotated version of this thread?
I hate you.
-
The topic changed in that conversation a while before that.
No; the topic still says "Sharp Regrest: Top 10 Worst C# Features". I'm looking at it right now.
-
No; the topic still says "Sharp Regrest: Top 10 Worst C# Features". I'm looking at it right now.
You're confusing the title with the content. This is easy to do when you aren't very good with words.
-
Hey as long as it says C# up there, you can't get mad at me for assuming you're talking about C#.
That's all I'm sayin'.
-
I'm not mad, just trying to help you understand.
-
Oh yes that mistake sure is fun to make.
I would have to disagree about for(;;) being less intuitive than foreach(). For someone just learning programming concepts, understanding that the alias you create in the foreach() declaration IS the object you want, instead of explicitly pulling it out using an index was certainly harder for me to comprehend.
I personally prefer explicit syntax. It aids understanding and prevents ambiguity (not that foreach() is ambiguous)
Also, does foreach() work on an object with no iterator?
Serious question, don't hate me if I'm dumb
-
I feel like it really isn't hard to parse.
Parsing is the process done by the compiler, not people. C and especially C++ parsing is a nightmare. The C++ grammar is total train-wreck as evidenced by the most vexing parse and the presence of
typename
andtemplate
keywords all over the place. In fact, C and C++ grammars are not context-free.C# is spared these two particular cases because it does not have constructor-call expression without
new
and it's generics are interface-bound. But the grammar is still more complicated than the pascal and VB ones.Nobody's comparing
for(;;)
towhile()
, we're comparing it toforeach()
.But comparing
for(;;)
towhile()
is the only comparison that makes sense. Becausefor(;;)
is an alternate syntax forwhile()
. For many cases a superior one, if for no reason then because it makes it less likely you forget the step expression.foreach()
exists. It should be preferred over the other two loops most of the time. Even for iterating over indices; I hope a iota operator (that yields numbers from n to m, optionally with step s) exists somewhere in the standard library.