𝄯 Sharp Regrets: Top 10 Worst C# Features



  • @Bort said:

    And it would depend on some proprietary UI control set that is out of date and you don't have a license for so the app won't run. (Dealing with this right now).

    I've spent a significant amount of my development time removing third-party controls from programs that made almost no use of their unique features. It's hugely valuable to me that my code is "checkout and run" ready. I hate bringing on help for a project and spending hours getting him/her up and running.

    Unfortunately, a student would probably chalk it up to their lack of experience rather than learn the real lesson. I hate classroom exercises where several students fall behind because they can't get up and running rather than because they are having trouble with what we are actually there to learn.



  • @boomzilla said:

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

    Derailed Thread is derailed.



  • @tar said:

    Bonus side rant: back in C++/C# land, ++x increments x and then uses the value in an expression. x++ uses the value of x in the expression and then increments it. x++ use, then increment; ++x increment, then use. I think Lippert just learned the wrong mnemonics...

    They both immediately evaluate but they have different return values. That's his main point.


  • Trolleybus Mechanic

    Speaking of C# and LINQ and shit. Design question:

    I have a custom object. Thing. It has properties like ID, Name, and SecretShit. Thing is fully serializable.

    I have a webservice. People can request a list of Things.

    I want to return them a list of Things, but I don't want them to see SecretShit. What's the least stupidest way to do this:

    1. Write a LINQ select for only ID and Name?
    2. Create some sort of wrapper object that only exposes ID and Name?
    3. Is there some fancy XML property shit I can do that will serialize the whole object, but selectively not SecretShit?
    4. Some other way I haven't thought of that won't end up on the front page?


  • You have two objects. You only think you have one.

    EDIT: Alternatively, you could .Select them and null all the things you don't want. Sounds awful, though.



  • @Lorne_Kates said:

    I want to return them a list of Things, but I don't want them to see SecretShit. What's the least stupidest way to do this:

    Lazy way: tell your serializer to not serialize SecretShit.

    Correct way: create a ViewModel object that represents, you know, the view model.



  • @Lorne_Kates said:

    I want to return them a list of Things, but I don't want them to see SecretShit. What's the least stupidest way to do this:

    Both WCF services and asmx-based services served as JSON via the ScriptService attribute will only serialize properties with the DataMember attribute on them. Both of @blakeyrat's methods require deciding where to put those attributes, the only difference will be which class/interface to put them on.



  • @Jaime said:

    Both of @blakeyrat's methods require deciding where to put those attributes, the only difference will be which class/interface to put them on.

    That's not true.

    If you create a ViewModel, you omit SecretShit from it entirely. Thus, there is no need to bother tagging with an attribute.



  • @blakeyrat said:

    Lazy way: tell your serializer to not serialize SecretShit.

    Correct way: create a ViewModel object that represents, you know, the view model.

    Sometimes both. With a lot of architectures, the view model will be part of the javascript on the client, so you can't filter there.



  • Ridiculous! That just leaves the number's sign unchanged, twice.


  • Java Dev

    @boomzilla said:

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

    That's the cases where you should've been using foreach. The only case I'm envisioning where you'd use while is if you should not have been using for() to begin with, because you're not iterating over a collection.

    I will admit I'd forgotten at the time of writing that that an iterator doesn't allow you to modify the collection (specifically deleting the current member), but there must be a better solution for that than duplicating the iteration logic.



  • @PleegWat said:

    ...an iterator doesn't allow you to modify the collection...

    Mutable collections?! How repulsive!



  • The best way: have the compiler automatically generate most of your ViewModels for you, and then do a single custom ViewModel to drop SecretShit.



  • @PWolff said:

    In an ideal world, there would be only one language that enables everyone to be Doing It Right™.

    DiscoPL?

    ducks


  • area_can

    @Magus said:

    And unless they start you with CL,

    Mine started us out on racket, and then a mix of racket/C, then C++, and for our intro to compilers course we had the choice of any of three languages. How did y'all's do it?



  • @Magus said:

    They both immediately evaluate but they have different return values. That's his main point.

    Well, in that case I don't really understand what his point is, and, seeing as he admits he can't tell x++ from ++x, I suspect it's not a very good point anyway.

    Probably due to the fact that I started writing C professionally back in '97, but the difference between preincrement and postincrement is so abundantly clear to me that I don't really know what to say to someone who doesn't get it (other than "learn harder...")



  • So, I'm kind of new to the topic...I looked through his article and I have to say: "Mr. Lippert, go write Pascal."

    Almost everything he's said has been in C since beginning of time. If there's anything that is clear these days, it is that BCPL has become the defacto progenitor of almost all families of languages:B, C, C++, Java, Javascript, PHP, Perl, ...you name it, the antecedent is BCPL. If these features are less than desirable, well, they are the undesirable features everyone knows.

    Note that I'm not saying he is exactly wrong about anything...but he's spitting into the wind.


  • Discourse touched me in a no-no place

    @CoyneTheDup said:

    the antecedent is BCPL

    Fun fact: I was taught by the designer of BCPL. He was mostly talking about software engineering (since it was all the waterfall stuff, I'm glad I slept through most of those lectures) but became much more animated when talking about programming language design. In retrospect, I suppose the SE course was something he taught because someone had to do it…



  • @blakeyrat said:

    Give me an example so I can tell you how you're wrong.

    Generating output text from an API-consumer supplied ordered list of items that should be presented as an indented list with numeral bullets.

    Plain text, no HTML! So you don't get to use the <ol> element as an escape hatch. Better yet! Scoring a little higher on practical applicability; let's say that you're actually implementing the rendering of such a type of layout construct in $rendering_engine_of_choice$.

    How are you going to do that without somehow keeping an index?



  • @dkf said:

    I suppose the SE course was something he taught because someone had to do it…

    I've sure taught my share of courses in (ugly) subjects because someone had to do it.



  • @Ragnax said:

    HTML [...] <ol> element

    Implementing this requires keeping track of an index of some sort, too.



  • @CoyneTheDup said:

    Almost everything he's said has been in C since beginning of time.

    Old => venerable => correct ?

    @CoyneTheDup said:

    Note that I'm not saying he is exactly wrong about anything...but he's spitting into the wind.

    The author of the informit article is very aware of his complaints being very much without any effect on C# development.

    Nevertheless, I appreciate that he is telling that anyway. And there is a little bit hope left, stiil.



  • @Ragnax said:

    How are you going to do that without somehow keeping an index?

    Like this, perhaps?

    public static string PrettyPrint(IEnumerable<String> input)
    {
        return string.Join("\n", Enumerable.Zip(
            input,
            Enumerable.Range(1, int.MaxValue),
            (val, num) => num + "\t" + val));
    }
    


  • @Salamander said:

    Enumerable.Range(1, int.MaxValue),

    Interesting solution. So, that's an object providing an auto-incrementing index. Definitively the most OO way of doing this without introducing objects just because Objects Are Good. Probably the more readable way for people used to OOP.

    (But what I dislike a bit is that the procedure first builds an array and then applies a Join on it. (Unless Join itself is implemented a different way which I can't tell.))

    But as to foreach: I've learned that foreach guarantees only that each element of a collection will be passed once and only once to the body of the loop, but explicitly doesn't guarantee a definite order of the elements. I've never encountered an example where the order changed between successive loops, though. Did the definition of foreach change in this point since I learned it?



  • @Salamander said:

    Like this, perhaps?

    You changed the way you generate the index and introduce it into the loop, but it's still an index.

    @PWolff said:

    (But what I dislike a bit is that the procedure first builds an array and then applies a Join on it. (Unless Join itself is implemented a different way which I can't tell.))

    It doesn't build arrays.

    IEnumerable sequences use pull semantics on top of the IEnumerator interface. The zip operator will only pull as many numbers from the range source as is necessary to process the input parameter and it produces again an IEnumerable sequence with pull semantics. string.Join has an overload that takes an IEnumerable<string> and there's possibly some kind of streaming operation happening there as well, rather than converting everything into an array.



  • That's string.Join (concatenation), not IEnumerable.Join (relational join).

    As for foreach and ordering, I think the order is guaranteed for all IOrderedEnumerable descendants.



  • @Ragnax said:

    How are you going to do that without somehow keeping an index?

    I wouldn't write code like that at all. It sounds like a terrible design, mixing content and design at exactly the wrong layer.



  • @PWolff said:

    (But what I dislike a bit is that the procedure first builds an array

    No it doesn't. Look up the implementation of Enumerable.Range().

    You need to internalize the yield keyword before you fully understand the "IEnumerable magic" in modern C#.

    @Ragnax said:

    You changed the way you generate the index and introduce it into the loop, but it's still an index.

    Right, but it's not a for(;;) loop, which was my point.



  • @PWolff said:

    But as to foreach: I've learned that foreach guarantees only that each element of a collection will be passed once and only once to the body of the loop, but explicitly doesn't guarantee a definite order of the elements. I've never encountered an example where the order changed between successive loops, though. Did the definition of foreach change in this point since I learned it?

    foreach is syntactic sugar over IEnumerator.Current and IEnumerator.MoveNext(). These are implemented by collection classes in the .NET framework, and are free to be implemented yourself.

    Enumerators that return indeterminate order do exist and some can even be explicitly random. E.g. many people at one point roll a random shuffle implementation as an extension method on IEnumerable<T> sequences.

    There is also no guarantee by foreach that each element will be passed only once. This is a guarantee on the indiviudual collection classes only. You can easily build an implementation of an IEnumerable or IEnumerator that returns each element twice.



  • @blakeyrat said:

    I wouldn't write code like that at all. It sounds like a terrible design, mixing content and design at exactly the wrong layer.

    At one point, you're going to have to take your content and spit back out somethng resembling the design.
    At that point, you're going to have to use an index in some way or form to add those bullets to the finished, processed output.

    This has nothing to do with separation of concerns (with which I agree, mind you) and everything to do that at some point during the pipeline from input to output you - will - need - that - index.



  • Fine. Whatever.

    I never said you wouldn't need an index. I said you wouldn't need a for(;;) loop.

    So you're arguing against ghosts and phantoms. Congratulations, you win your argument against an imaginary hypothetical post that doesn't resemble anything I typed. Give yourself an award.


  • Discourse touched me in a no-no place

    @Ragnax said:

    At that point, you're going to have to use an index

    Very often not. Most of the time, all that a system needs to know about indices can be inferred at the point it needs the info from the point in the sequence it's at. It usually doesn't care where the generator is in the generator's own processing counter.



  • @Ragnax said:

    It doesn't build arrays.

    [...]

    string.Join has an overload that takes an IEnumerable<string> and there's possibly some kind of streaming operation happening there as well, rather than converting everything into an array.

    You're right, I've tried it. The Enumerable.Zip is evaluated before the first value of IEnumerable<String> input is requested.

    @blakeyrat said:

    You need to internalize the yield keyword before you fully understand the "IEnumerable magic" in modern C#.

    My problem was with the string.Join(), though. TIL that the Microsoft guys did this the right way.

    Edit: I've written procedures that emulate the functionality of yield return decades ago. When the only environment I had was a SHARP pocket pc. With one text line with 26 characters. Only programming language BASIC. With line numbers. And all variables global. And IF (condition) THEN (line number); and no ELSE. (So all I thought when I read about yield the first time was, "finally!")

    @Ragnax said:

    You can easily build an implementation of an IEnumerable or IEnumerator that returns each element twice.

    That wouldn't be what I'd expect of a foreach, but ok.

    @blakeyrat said:

    I never said you wouldn't need an index. I said you wouldn't need a for(;;) loop.

    Must have missed the for(;;) loop part..

    But you're right that we don't need an index of the itereated elements.

    Anyway, I'm making progress in abstracting from the troll tone of your posts.



  • @PWolff said:

    @blakeyrat said:
    I never said you wouldn't need an index. I said you wouldn't need a for(;;) loop.

    Must have missed the for(;;) loop part..

    I missed that, too.

    @blakeyrat said:

    @xaade said:
    There are cases where you need an index in your iteration.

    Give me an example so I can tell you how you're wrong.

    Funny, I still don't see it.



  • OH SNAP hoisted.

    Ok well I lose everything. Come here and kill my cat I guess.


  • Java Dev

    @PWolff said:

    Edit: I've written procedures that emulate the functionality of yield return decades ago. When the only environment I had was a SHARP pocket pc. With one text line with 26 characters. Only programming language BASIC. With line numbers. And all variables global. And IF (condition) THEN (line number); and no ELSE. (So all I thought when I read about yield the first time was, "finally!")

    I've done the same in C, rather recently. I had two possible sources, an optional intermediate step that would yield more items than it returned, and a final step that built a hash table out of it - emulating pull enumerators with function pointers and void arguments was easier than the alternative approaches I could think of.



  • I've solved all the bit fiddling problems! Let's just use a language that aims to:

    • Extend the ease of string processing in C to other, more basic, data types such as integers.
    • Extend the terseness of COBOL to more fundamental code constructs, such as numeric literals.


  • @ben_lubar said:

    terseness of COBOL

    oh, see, now that was subtle...


  • Banned

    OK, I just started small project and almost immediately stumbled upon three small annoyances in C#:

    1. There's no syntax for ignoring lambda function arguments. Yes, I can write identifiers and just ignore them, but it's ugly.
    2. Lambdas cannot be assigned to variables declared with var.
    3. When a variable is being initialized with lambda, it cannot call itself. Workaround is to initialize to null and then subsequently assign the lambda. This is an obvious bug in the compiler.


  • var x = (() => x)();
    


  • @ben_lubar said:

    I've solved all the bit fiddling problems! Let's just use a language that aims to:

    And we have already a name for it! tar coined it here.


  • Banned

    @Buddy said:

    var x = (() => x)();

    Not only it's not what I was talking about, but also it doesn't compile.



  • c# specification states that you cannot use the value of a variable inside it's initializer. That is a very good rule, and if you would think about the snippet I posted for even a second, you would see that there's no way you could possibly relax it to allow your point c to work without creating an unnecessary mindfuck.



  • @Ragnax said:

    You changed the way you generate the index and introduce it into the loop, but it's still an index.

    Define 'index', because I didn't generate an index at all; I generated the list numbers, in the order that they need to appear.
    I could easily change Enumerable.Range(1, int.MaxValue) to Enumerable.Repeat("*", in.MaxValue) to get a bulleted list, which obviously has no relation to indexes at all.


  • Discourse touched me in a no-no place

    @PWolff said:

    I've written procedures that emulate the functionality of yield return decades ago.

    Were you doing it by writing the code using an ordinary function that just returned the next value, or were you doing it by managing the stack context such that you could write code with the same sort of structure as C# with yield return? The two things are quite different; the transformation to continuation-passing-style has been pretty well known for many years (i.e., it was old when I found out about it a couple of decades ago).

    But to claim that you can do something equivalent to yield return when you're doing the transformation yourself? I don't accept that. The whole point is that while it is theoretically possible to do the transform by hand — there's no theoretical increase in capability from the functionality — it's actually really annoying to do in complicated code, and the compiler does a much better job of it, as it is much better at building the state management code than you are. It does this by being able to save the state of the local variables and to be able to resume executing a function from a place other than the first instruction of the function.

    I don't know if C# has the next level of functionality of that feature though, a full coroutine that can yield from locations other than the root stack frame of the coroutine, so that other functions can yield on behalf of things that call them. I suspect they haven't; it has quite a bit of impact on things elsewhere, such as on determining whether a function is a generator by whether it is doing yield return and changing its type appropriately, even if it is a very interesting extension (it would subsume a large bunch of other things, some not immediately obviously).



  • @Gaska said:

    Lambdas cannot be assigned to variables declared with var.

    Nemerle spells var as def, (Actually, to be pedantic, it spells var as mutable and something like const var as def, if const var is even a C# thing...) and it has no problem assigning lambdas to variables, along with other useful tricks like currying and tuple destructuring. Oh yeah, it has type inference as well, so you hardly ever need to assign a variable a type.

    using Nemerle.IO;
    
    def x = 1;
    def (y, z) = (2, 3);
    def l = x => { x * 2 };
    def m = (a, b) => { a / b };
    def n = m(_, 2.0);
    def o = m(2.0, _);
    def a = l(x);
    def b = m(y, z);
    def (c, d) = (n(z), o(z));
    
    print("\na=$a\nb=$b\nc=$c\nd=$d\n");
    
    a=2
    b=0.666666666666667
    c=1.5
    d=0.666666666666667
    
    

  • Banned

    @Buddy said:

    c# specification states that you cannot use the value of a variable inside it's initializer. That is a very good rule

    Yes, it is.

    @Buddy said:

    and if you would think about the snippet I posted for even a second, you would see that there's no way you could possibly relax it to allow your point c to work without creating an unnecessary mindfuck.

    The problem with your snippet is that you're calling your lambda that returns x in the initialization of variable x. It's obvious use of uninitialized variable. But notice that if we were to omit the call, ie. write it as
    var x = () => x;, there's no use of uninitialized variable, because you're not calling your lambda until after you initialized variable x. Paired with that in C#, closures are resolved on lambda call, not on lambda declaration, I don't see anything that should disallow such usage, except buggy compiler.


  • Java Dev

    @dkf said:

    Were you doing it by writing the code using an ordinary function that just returned the next value, or were you doing it by managing the stack context such that you could write code with the same sort of structure as C# with yield return? The two things are quite different; the transformation to continuation-passing-style has been pretty well known for many years (i.e., it was old when I found out about it a couple of decades ago).

    I know in my case I was using an ordinary function, and saved my own state. If you need the actual yield return pattern in C, you'd probably have to switch to a new stack and use coroutines - I don't think you can transform to only save the state and run in the existing stack if the compiler doesn't support that.

    Then again, wonder if this approach would work:

    #define YIELD(s,n,x) do { s.pos = n; return x; case n: } while(0)
    
    struct state {
        int pos;
        int i;
    };
    
    int function( struct state * s )
    {
        switch( s.pos ) { case 0:
    
            for( s.i = 0 ; s.i < 10 ; s.i++ )
            {
                YIELD(s, 1, i);
            }
        }
    }
    

    Only downside is you'd have to manually number your yield locations, initialize state, and put local variables that need to be retained inside the struct.


  • Banned

    Another small C# annoyance: when doing switch, you cannot fall-through, yet you have to write break.



  • @Gaska said:

    Lambdas cannot be assigned to variables declared with var

    How would you determine the lambda type? Even assuming you specify the lambda argument types directly, you still don't know whether the user wants a DIY delegate type, a Func<>, an Expression<Func<>>, or some other type assignable from a lambda I've probably missed.


Log in to reply