𝄯 Sharp Regrets: Top 10 Worst C# Features



  • Though C# has many great features, a handful could have been designed differently or omitted entirely, says Eric Lippert, who should know, because he served on the design committee. The co-author of Essential C# 6.0, Fifth Edition shares his personal top 10 (bottom 10?) C# design annoyances.


  • The most compelling thing I saw in the article was at the end, in the "Dishonorable Mentions" section:

    Use of the += operator on delegates and events has always struck me as weird. It also works poorly with generic covariance on delegates.

    Everything else is bitching about how this C-like language is similar to C which is supposedly confusing to some people.



  • I read this yesterday.

    I agree with 9, 8, 4, 3 and 1.

    In fact 1 is really annoying because each new version of C# adds features that move more and more type errors to run-time instead of compile-time. LINQ added a bunch, the new async/await stuff adds a bunch more... (and breaks debugging as well!) Ugh.


  • Discourse touched me in a no-no place

    That's a good one, though the ones about : and void are even better.

    However, if we stick to just Eric's top ten: array covariance is fundamentally broken. It's broken in Java too. Some design decisions are just plain wrong. And finalizers are a very sharp tool indeed; you can use them to do serious mischief.



  • ####10: The empty statement does nothing for me

    Meh, don't mind it.

    ####9: Too much equality

    Operator overloading is a terrible idea in general. Get rid of that, and you get down to 3 methods. Which is still too many, probably.

    ####8: That operator is shifty
    Like many other C-based languages, C# has << and >> operators that shift the bits in the integer left and right. They have a number of design problems.

    Don't care, never had much use for these.

    ####7: I'm a proud member of lambda lambda lambda
    C# 2.0 added anonymous delegates. Note that this is quite a "heavy" syntax; it requires the keyword delegate, the argument list must be typed, and the body is a block containing statements. The return type is inferred. C# 3.0 needed a far more lightweight syntax to make LINQ work, where all types are inferred and the body can be an expression rather than a block:

    Ok, agreed. We could have gone without delegates.

    ####6: Bit twiddling entails parentheses

    I don't care, I always use parentheses anyway. Better safe than sorry.

    ####5: Type first, ask questions later
    Okay, dim is a little weird in VB, but these and many more languages follow the very sensible pattern of "kind, name, type": What kind of thing is it? (A variable.) What is the variable's name? ("x") What is the type of the variable? (A number.)

    By contrast, languages such as C, C#, and Java infer the kind of the thing from context, and consistently put the type before the name, as if the type is the most important thing.

    I prefer it the C way. But I guess it's mostly just habit.

    ####4: Flag me down
    In C#, an enum is just a thin type-system wrapper over an underlying integral type. All operations on enums are specified as actually being operations on integers, and the names of enum values are like named constants.

    Hard to imagine enums any other way. If you want to have bitmasks, you need enums as integers.

    Not sure about this one.

    ####3: I rate plus-plus a minus-minus

    And I rate it plus-plus to infinity!

    What's up with all the ++ hate? That thing is just a pleasure to type and is very distinct when looking at code. Just never rely on its strange prefix/postfix semantics and you're good.

    ####2 I want to destruct finalizers

    Absolutely agree. These things never made sense to me. They should go away.

    ####1 You can't put a tiger in the goldfish tank, but you can try
    This feature is called "array covariance," and it allows developers to deal with the situation in which they have an array of goldfish in hand, they have a method they didn't write that takes an array of animals, the method only reads the array, and they don't want to have to allocate a copy of the array. Of course, the problem arises if the method actually does write to the array.

    Good point. I remember getting fucked by this a few times.


    Nothing about linq SQL-like syntax, with 1000 stupid little keywords? That's my no 1 peeve.


  • I survived the hour long Uno hand

    @cartman82 said:

    Just never rely on its strange prefix/postfix semantics and you're good.

    I feel like all the valid criticisms would go away if you defined ++ to return void rather than return a value at all, in both prefix and postfix positions. So you could do x++, but you couldn't use the value returned in a chain of ugly.


  • Discourse touched me in a no-no place

    @cartman82 said:

    Hard to imagine enums any other way.

    Java's enums are objects. You won't be using them as bitmasks any time soon…



  • Just saw the honorable mentions.

    The for loop has bizarre syntax and some very rarely used features, is almost completely unnecessary in modern code, and yet is still popular.

    Don't know what he means here.

    Use of the += operator on delegates and events has always struck me as weird. It also works poorly with generic covariance on delegates.

    Oh yes. This.

    The colon (:) means both "Extends this base class" and "Implements this interface" in a class declaration. It's confusing for both the reader and the compiler writer. Visual Basic spells it out very clearly.

    Agreed.

    The rules for resolving names after the aforementioned colon are not well founded; you can end up in situations where you need to know what the base class is in order to determine what the base class is.

    *shrugs*

    The void type has no values and cannot be used in any context that requires a type, other than a return type or a pointer type. It seems bizarre that we think of it as a type at all.

    What's the alternative? Something like undefined value, which brings its own host of problems? No thanks.

    Static classes are how C# does modules. Why not call them "modules"?

    Never thought of it that way. Makes sense.

    No one would shed tears if the unary plus operator disappeared tomorrow.

    I wouldn't.



  • @Yamikuronue said:

    I feel like all the valid criticisms would go away if you defined ++ to return void rather than return a value at all, in both prefix and postfix positions. So you could do x++, but you couldn't use the value returned in a chain of ugly.

    Hmm, so you either have side effects or not. Not these mixed confusing uses.

    Ok, I could live with that.


  • Discourse touched me in a no-no place

    @cartman82 said:

    What's the alternative?

    Some languages have a unit type, with exactly one value of that type (also called unit usually). Since there's only one value, it doesn't need to be communicated around as it cannot actually be carrying any information. It works like void but without the suck!



  • @dkf said:

    Some languages have a unit type, with exactly one value of that type (also called unit usually). Since there's only one value, it doesn't need to be communicated around as it cannot actually be carrying any information. It works like void but without the suck!

    So like undefined.



  • @cartman82 said:

    >The for loop has bizarre syntax and some very rarely used features, is almost completely unnecessary in modern code, and yet is still popular.

    Don't know what he means here.

    Maybe it's the same shit with many "functional" programmer that don't like the C style of for loops and instead use arr.each(...)



  • @cartman82 said:

    > The for loop has bizarre syntax and some very rarely used features, is almost completely unnecessary in modern code, and yet is still popular.

    Don't know what he means here.

    I believe he's talking about having multiple distinct statements within parenthesis, separated by semicolons.
    It's certainly not syntax used anywhere else that I can remember.



  • @Salamander said:

    believe he's talking about having multiple distinct statements within parenthesis, separated by semicolons.It's certainly not syntax used anywhere else that I can remember.

    Yeah. But try using while loop in a similar fashion, and you'll get annoyed with having to set everything up yourself. IMO the very ritual of having 3 little boxes where you write in things helps you avoid endless loops.



  • I honestly don't remember the last time I used the standard for loop. I'm normally using either a for-each loop, and the rest of the time I just use a while loop.



  • @cartman82 said:

    >2 I want to destruct finalizers

    Absolutely agree. These things never made sense to me. They should go away.

    No developer should ever write one. The framework provides everything a developer should ever need with the SafeHandle class and Dispose().

    Does C# even need finalizers? Probably. If you only used SafeHandles, a finalizer would only ever run if an object went out of scope without being disposed. Without the finalizer, that resource would leak until the process ended.

    If something like SafeHandle was built into the language, we'd get the benefits of the finalizer without any developer screwing it up by trying to write their own. But what would that syntax look like? Inheriting from SafeHandle is pretty verbose even for simple resources.



  • They're gross and every time I have ever needed their features I'm infinitely better off emulating it with named final ints



  • enums as ints are great when you're writing a C# wrapper for a C API that uses enums for return codes. This probably doesn't happen very often, but it does where I work.

    Those working in C# don't have to pull up the C header files to get return codes. They can just do XXXReturnCode. and IntelliSense plops down the full list for them.



  • What I got from his point about enums was that he'd rather there were two types. Event thinking about casting an arbitrary int to a non-flags enum should make your skin crawl. Then too, flags enums need an attribute, when a keyword would be more appropriate. So I can agree with that.



  • Go doesn't have a name for its "void type". A function that returns an int would be something like func Foo() int and a function that doesn't return anything would be like func Foo().



  • ####10: The empty statement does nothing for me

    This point seems all over the place.
    I don't like empty statements, except their useful for goto, the most baneful thing in modern programming.

    ####9: Too much equality

    I'm ok with getting the operators for free with one exception.

    Value and Referential equality is a bitch.
    It's a bitch in every language, and no language does it right.

    You shouldn't get explicit operator overloading.

    Operators should be available only to classes that implement the base methods .Equals and .Compare, and that's it.
    Operators should not be able to be overloaded directly.
    And Referential equality should be limited to a sealed object method.

    ####8: That operator is shifty
    Like many other C-based languages, C# has << and >> operators that shift the bits in the integer left and right. They have a number of design problems.

    If you want more guarantees, just use multiplication.
    If you want a series of bits.... Make the type. It's not that hard.

    ####7: I'm a proud member of lambda lambda lambda

    You still have to state a parameter receiving a delegate explicitly, even if you send it an anonymous method.

    ####6: Bit twiddling entails parentheses

    Either use an enum, or make your bit class.

    ####5: Type first, ask questions later
    Okay, dim is a little weird in VB, but these and many more languages follow the very sensible pattern of "kind, name, type": What kind of thing is it? (A variable.) What is the variable's name? ("x") What is the type of the variable? (A number.)

    By contrast, languages such as C, C#, and Java infer the kind of the thing from context, and consistently put the type before the name, as if the type is the most important thing.

    I'd love to be able to use var in declarations that are immediately assigned.
    But sometimes you have to include type information.
    This is really a grass is greener argument here.

    ####4: Flag me down

    Don't underestimate the advantages to being able to treat enums as other value types. It makes serialization much easier. I wouldn't want to lose this.

    ####3: I rate plus-plus a minus-minus

    I only ever use these as stand alone statements, so they don't matter much at all.

    ####2 I want to destruct finalizers

    Never used them.

    ####1 You can't put a tiger in the goldfish tank, but you can try

    Linq solves most of these problems for me. I can see it there inline.

    The for loop has bizarre syntax and some very rarely used features, is almost completely unnecessary in modern code, and yet is still popular.

    Then give me a counter in the foreach. I'm tired of explicitly writing counters or indexes for for loops because foreach loops fail to provide something that's so obvious.

    Also, I can quickly navigate backwards, or every other item, generally things that would be cumbersome in LINQ.

    If you don't like it, don't use it.




  • @cartman82 said:

    Don't know what he means here.

    99% of time, you actually want foreach (the other 1% is itself 90% "I'm modifying stuff in the underlying structure" and 10% "I actually want a counter"). And the syntax, if you're not used to it, is not self-evident (was it initialization-end of loop statement-condition? Nah, I think those two last ones go the other way around...)

    @cartman82 said:

    The colon (:) means both "Extends this base class" and "Implements this interface" in a class declaration. It's confusing for both the reader and the compiler writer. Visual Basic spells it out very clearly.

    It's only an issue if you don't mark your interfaces with leading I, at which point you're a horrible human being and deserve every consequence of it.



  • True, but the C style of for() is overly baroque for 95% of the uses it is put to, and the other 5% are mostly misuses that could have been coded more clearly without it. For most purposes, a general definite loop doesn't need to have a general-purpose initializer, it only needs a starting point; it doesn't need a general test, just a count; and doesn't need an explicit iterator at all, just an optional step value. Consider the Pascal style of FOR:

    FOR i := 1 TO 5 DO
    BEGIN
         {  do something here }
    END;
    

    Verbosity aside, this captures the most common usage case for a general definite loop. A C equivalent could easily have been:

    for (i = 1..5) 
    {
        /* do something here */
    }
    

    Ironically, the generalization of the for loop also made it harder for the compiler (as opposed to the programmer) to optimize the loop.

    In any case, modern languages such as C# more often use a for-each construct iterating over a collection, rather than a general definite loop. I'm not saying that the C for() doesn't have its advantages, and it is far too late to change it in any case, but its flaws cannot be ignored by designers of newer languages.

    Oh, well, it could have been worse; Algol 68 had a FOR construct that was absolutely nuts, with something like 12 different optional qualifiers.



  • @Maciejasjmj said:

    It's only an issue if you don't mark your interfaces with leading I, at which point you're a horrible human being and deserve every consequence of it.

    VS colors them differently.



  • @Maciejasjmj said:

    (the other 1% is itself 90% "I'm modifying stuff in the underlying structure" and 10, 9.9% "I actually want a counter", and 0.1% working with two linked collections simultaneously)

    FTFM

    @Maciejasjmj said:

    It's only an issue if you don't mark your interfaces with leading I, at which point you're a horrible human being and deserve every consequence of it.

    QFT



  • @abarker said:

    and 0.1% working with two linked collections simultaneously

    I write classes for that.... if it's complicated.

    If it's simple, use a LINQ join.

    These people be lazy.


  • BINNED

    @Maciejasjmj said:

    It's only an issue if you don't mark your interfaces with leading I, at which point you're a horrible human being and deserve every consequence of it.

    Haskell typeclasses are, to oversimplify, similar to interfaces, but Haskell coders for some reason don't find it necessary to mark each interface with a leading TC.

    Objection overruled.

    plus what @xaade said



  • @ScholRLEA said:

    Verbosity aside, this captures the most common usage case for a general definite loop. A C equivalent could easily have been: for (i =1..5)

    That's assuming that people only ever want to increment by ±1. There have been times where I need to increment by 0.1, 2, or 10. How would I do that in Pascal? How would I do that in your proposed syntax?



  • I've been moving to using the old winforms posfix naming, because I found out that there's no real reason to shorten names.

    It's a good indicator for DoingItWrongTM (MyTypeFactorySingletonFactoryTools)

    So

    AnimalInterface

    Bat : AnimalInterface



  • @xaade said:

    old winforms posfix naming

    I don't know what the "standard" is but most Winforms tutorials I've seen use btnOk, lstItems, etc prefix naming. I use postfix myself, and let autocomplete handle it.



  • That's what I was referring to when I mentioned a step value. Standard Pascal didn't have a way to do this, but some dialects did:

    FOR x := 0 DOWNTO -1000 BY -10 DO 
    

    Admittedly, this was an extension syntax for Pascal, but C could have done something like

    for (i = 0..-1000 by -10)

    true, it means adding another reserved word, but that's not that big a price to pay.

    Stepping by a float value could be problematic, I will grant you that much, but it could be done even with this syntax. As I said, the C syntax does have it's advantages, I'm just not sure that they justify the extra complexity.

    EDIT: fixed a mistake in the Pascal syntax.



  • This is why I'm a WPF programmer.

    No more clunky control names.



  • Reserved words? No, the C way would be to add yet another meaning to an already used character.



  • @xaade said:

    @Maciejasjmj said:
    It's only an issue if you don't mark your interfaces with leading I, at which point you're a horrible human being and deserve every consequence of it.

    VS colors them differently.

    ??

    Taken from VS 2013



  • @xaade said:

    @abarker said:
    and 0.1% working with two linked collections simultaneously

    I write classes for that.... if it's complicated.

    If it's simple, use a LINQ join.

    These people be lazy.

    It's the code I inherited.



  • This is true. I'm not sure which one it would be in this case, though.





  • Maybe he uses resharper? I know I prefer them colored differently, and static classes as well.



  • In VB.NET, there's the STEP keyword:

        For i As Integer = 0 To 10 Step 2
            console.writeline(i & " is an even number!")
        Next
    


  • Though to be honest, mine probably aren't anymore. I used the color themes extension to make my entire IDE solarized. I quite like the result.



  • @xaade said:

    What color scheme is that*? Or did you make a custom one?

    * So I can avoid it. My eyes are burning!

    @Magus said:

    Maybe he uses resharper? I know I prefer them colored differently, and static classes as well.

    Ah. Is that it, @xaade? Are you using ReSharper to get the different colors?



  • I think it's down to the colour scheme. My VS 2012 has them coloured differently.



  • @abarker said:

    What color scheme is that*? Or did you make a custom one?

    That just looks like the dark one, actually. VS comes with two, and three for the main UI.


  • BINNED

    @xaade said:

    AnimalInterface

    Bat : AnimalInterface

    Good $DEITY, that's even worse!

    For comparison, here are some typical Haskell typeclass names from the Prelude:

    • Bounded
    • Read
    • Show
    • Foldable
    • Traversable


  • Annnnd... this is part of why I like Lisp: if I don't like the syntax for a particular construct, I can write a macro that gives me whatever syntax I want.

    Well, within the confines of s-exprs, that is; that seems to be a showstopper for most people, but you'd be surprised how much flexibility a language like Common Lisp can give you even then. The intro of the book Let Over Lambda claims to give some examples of Lisp code that would pass for C if you didn't know that it was actually all built with read-macros. Weird, but possible, I guess, though I haven't gotten that far in the book yet so I'm not sure.

    The down side of this is that it involves writing macros, which can be a pain; it lets every Lisp coder come up with their own private dialect that no one else can read; and it can make for some truly bizarre write-only code. But that's what makes for horse races, I guess.

    Filed under: I'm expecting a request to define 'read-macro' in 3, 2, 1...



  • @abarker said:

    Ah. Is that it, @xaade? Are you using ReSharper to get the different colors?

    nope.

    But I can't find the font colors for C# either.



  • Tools, Options, Environment, Fonts and Colors, Text Editor. Most of the settings are C#, unless they specify otherwise.



  • @abarker said:

    @Maciejasjmj said:
    (the other 1% is itself 90% "I'm modifying stuff in the underlying structure" and 10, 9.9% "I actually want a counter", and 0.1% working with two linked collections simultaneously)

    FTFM

    Why not just Zip them?



  • @hungrier said:

    Reserved words? No, the C way would be to add yet another meaning to an already used character.

    s/C/C++/



  • @Maciejasjmj said:

    It's only an issue if you don't mark your interfaces with leading I, at which point you're a horrible human being and deserve every consequence of it.

    Hungarian Notation in disguise.

    I know it's an interface, it says "interface" before the name.

    I know it's an abstract class, it says "abstract class" before the name.

    I know it's a base class, there are classes extending it.

    I know it's an exception, it's being thrown or caught.


Log in to reply