𝄯 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.



  • @Gaska said:

    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.)



  • @Buddy said:

    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.



  • @Buddy said:

    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.)



  • @PWolff said:

    Matter of taste

    POST IS MOST!


  • ♿ (Parody)

    @PleegWat said:

    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.


  • Discourse touched me in a no-no place

    @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 a while, you may have to put copies of the step clause in several locations.



  • @boomzilla said:

    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?



  • @dkf said:

    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.)


  • Discourse touched me in a no-no place

    A finally would be one-shot unless you put it inside the loop.


  • ♿ (Parody)

    @dkf said:

    @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 a while, 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.


  • Discourse touched me in a no-no place

    @mrguyorama said:

    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.


  • ♿ (Parody)

    @PWolff said:

    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 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.



  • That's what I thought. Is there a readability penalty?



  • @boomzilla said:

    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?



  • @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.



  • @ScholRLEA said:

    @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™.



  • @PWolff said:

    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".


  • Banned

    @Maciejasjmj said:

    everlasting ecstasy


  • Discourse touched me in a no-no place

    @mrguyorama said:

    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 a step.



  • @Maciejasjmj said:

    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 to object would result in null, which is the more dynamic variant of “nothing to see here” and which both those languages unfortunately have.

    @dkf said:

    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 as object representation of void.

    @xaade said:

    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).

    @xaade said:

    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.

    @xaade said:

    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.

    @Gaska said:

    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 &.

    @Buddy said:

    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.

    @Gaska said:

    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.

    @PWolff said:

    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++);
    

    @Buddy said:

    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.

    @PWolff said:

    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!


  • Banned

    @Bulb said:

    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.

    @Bulb said:

    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.



  • @Gaska said:

    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.



  • @Bulb said:

    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.)

    @Bulb said:

    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.

    @Bulb said:

    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.


  • Discourse touched me in a no-no place

    @PWolff said:

    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).



  • @mrguyorama said:

    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.



  • @Gaska said:

    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 that event += subscriber is equally understandable.

    @Gaska said:

    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.

    @Bulb said:

    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.

    @Bulb said:

    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.



  • @Bulb said:

    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.

    @Maciejasjmj said:

    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.

    @xaade said:

    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.


  • Trolleybus Mechanic

    @blakeyrat said:

    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?



  • @Kian said:

    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!


  • @Maciejasjmj said:

    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.



  • @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.

    Nobody's comparing for(;;) to while(), we're comparing it to foreach().

    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.



  • @boomzilla said:

    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.


  • ♿ (Parody)

    @blakeyrat said:

    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.

    @blakeyrat said:

    You do that in LINQ now.

    I probably would if I used C#, which I don't.


    Filed Under:Shamalama plot twist!



  • @mrguyorama said:

    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.



  • @boomzilla said:

    Keep up with the discussion, please.

    Then what the fuck were you talking about?

    @boomzilla said:

    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.)

    @boomzilla said:

    I probably would if I used C#, which I don't.

    Then why the fuck are you posting in here?



  • @boomzilla said:

    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.


  • ♿ (Parody)

    @blakeyrat said:

    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.



  • @boomzilla said:

    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.


  • ♿ (Parody)

    @blakeyrat said:

    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.

    @blakeyrat said:

    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?

    @blakeyrat said:

    I hate you.

    💏



  • @boomzilla said:

    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.


  • ♿ (Parody)

    @blakeyrat said:

    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'.


  • ♿ (Parody)

    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



  • @mrguyorama said:

    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 and template 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.

    @blakeyrat said:

    Nobody's comparing for(;;) to while(), we're comparing it to foreach().

    But comparing for(;;) to while() is the only comparison that makes sense. Because for(;;) is an alternate syntax for while(). 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.


Log in to reply