𝄯 Sharp Regrets: Top 10 Worst C# Features
-
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).
You're in the cube next to me, aren't you.
-
Duh. But there's nothing violating the contract.
Maybe they expected their developers to be human beings instead of pedantic dickweeds.
-
I feel like some of the need is reduced if you're using Rx. I was planning to use it for my game's input, but it freaked out my friend who's working on it with me.
Some feminist is going to somehow disqualify you from winning any awards anyway, might as well just give up.
-
No, they already lost that war. You can keep fighting for them though, I guess?
-
Maybe they expected their developers to be human beings instead of pedantic dickweeds.
The best developers are all pedantic dickweeds. It's the key skill.
-
Another small C# annoyance: when doing switch, you cannot fall-through, yet you have to write break.
You can have fall-through in C#. Except it is a :
switch(x) { case 1: doThis(); goto case 2; case 2: doThat(); goto case 3; case 3: doOtherwise(); goto case 1; // yesss, yesss, shoot us in the footses };
For some reason I thought you can write
continue
to fall through, but the documentation does not mention that, so I probably saw that in some other language.
-
Another small C# annoyance: when doing switch, you cannot fall-through, yet you have to write break.
That's just a minor annoyance – they implemented a
fixworkaround. They could have implemented a nice fine fall-through, but nooooo, why doing it the simple (and right) way when there is such a wonderful, shiny, a bit more complicated, and broken way to do it:goto case
.Yes, it is as broken as it sounds. The following works exactly as
intendedexpected:static int testGotoCase(int n) { Console.WriteLine(); Console.WriteLine("testGotoCase(" + n.ToString() + ")"); // notify which function we are in switch (n) { case 1: Console.WriteLine("switch (n) -> case 1"); // notify where we are goto case 4; case 2: Console.WriteLine("switch (n) -> case 2"); // notify where we are goto case 3; case 3: Console.WriteLine("switch (n) -> case 3"); // notify where we are return 3000; case 4: Console.WriteLine("switch (n) -> case 4"); // notify where we are goto case 1; default: Console.WriteLine("switch (n) -> default"); // notify where we are goto case 2; } }
Edit: 'd by @Bulb
Edit 2: Nevertheless, the possibility to jump back is a proper .
-
You can have fall-through in C#.
Explicit goto isn't fall-through. Learn your terminology, OK?They could have implemented a nice fine fall-through
I would hate them even more if they did it.Nevertheless, the possibility to jump back is a proper .
Not really, if you remember that it's plain old goto (as indicated by keyword). At least you cannot goto into middle of a loop...
-
You'd break a lot of things by abusing IEnumerable for cyclic structures. Such as every goddamn extension method available. So by all means, you may fool around with it on your spare time for lulz, but write something like that in production code and I'd shoot you in the face.
-
write something like that in production code and I'd shoot you in the face
So you assume that every collection is small enough to conveniently fit into memory?
-
abusing IEnumerable for cyclic structures
(Emphasis by me)I suppose @donk buttumes a collection must be non-cyclic.
-
I suppose @donk buttumes a collection must be non-cyclic.
While I fully admit that most of my structures are non-cyclic, there's nothing wrong with them when they're the right tool.
-
Can we agree on
foreach
being a somewhat misleading name when used for a cyclic loop?
-
Especially if there's a yield in it.
Which is the case for almost every LINQ extension method - they don't enumerate until you call ToList or similar. So I don't think an infinite collection would break any of them.
-
Infinite is not quite the same as cyclic.
(In group theory, the additive group of integers is cyclic and infinite, though.)
-
You'd break a lot of things by abusing IEnumerable for cyclic structures. Such as every goddamn extension method available.
I'm not even a C# programmer and I know that's not true.
Here's a list of all the LINQ extension methods by execution type.
All "deferred streaming" operations work fine on infinite collections, no "deferred non-streaming" methods work, and some "immediate" work (eg First, ElementAt, and Single).
-
Hyperbole etc.
ToList is an obvious problem, but I also can't imagine Sum, Min, Max, All, Aggregate, Average, Distinct, Count, Last, OrderBy et al working very well at all if the enumeration doesn't terminate.
-
I've always viewed an enumerable as a mathematical sequence - can be both finite and infinite. The whole 'no duplicate elements, no restart, etc' thing seems mostly related to an enumerable on a collection in memory.
-
Many of those operations can conceptually have analogues on infinite streams (e.g., a running average as a stream instead of the average of the whole stream or the tail of a stream past a certain point as a stream instead of the last element). But you have to think in terms of infinite sequences. If you start out by using the mental model “this came from an array somewhere in memory” then you'll go wrong.
-
Certainly, but that's not the issue here, at least not to me. My gripe is with the hypothesized IEnumerable abuse breaking stuff one may reasonably expect to work, which goes against the principle of least surprise and as such would be a very bad idea in a public library or API of some sort. Or in short: don't do it (unless you're just fooling around).
-
I also can't imagine Sum, Min, Max, All, Aggregate, Average, Distinct, Count, Last, OrderBy et al working very well at all if the enumeration doesn't terminate.
Most of those are immediate operations which I already said may not work.
OrderBy never works as it's not streamed, and Distinct is an oddity which works fine for infinite lists but not cyclic lists.
-
Distinct is an oddity which works fine for infinite lists
Well, I have a very old machine here, with only 4 GiByte memory.
foreach (int i in System.Linq.Enumerable.Range(0, int.MaxValue).Distinct())
tends to get a bit slow after a few dozen million loops, and throws an OutOfMemoryException after less than 200,000,000 loops.
-
Yes, to work out if an element has been seen before, it needs to keep track of the elements it has already seen. What's your point?
Its runs, returns the output that is expected, and will still terminate the moment you stop reading it, either by breaking out of the loop or by usingTake
.
It only fails when you don't have the physical memory needed to run the code, but that's pretty much the same scenario for all programs. Including that snippet you wrote, which isn't even an infinite enumerable, just a large one.
-
I would be very careful with Distinct on non-terminating enumerables. You're unlikely to know on the consuming end when to stop, and apart from the memory problem there may be a finite set of input elements, not just in cyclic lists, but also for example in an enumerable that returns (pseudo-)random values in a certain range.
-
Psuedo-random is still just a cyclic list though.
Pretty much the only time you can get an non-cyclic infinite list of finite values is when you throw in a pure random number generator into the mix, at which point you're doing something so extremely wrong nothing will save whatever monstrosity was just coded.
That being said, needing to use distinct on an infinite list with infinite values means you're probably doing something wrong, but it's still a valid operation.
-
Another small C# annoyance: when doing switch, you cannot fall-through, yet you have to write break.
I don't know, I've seen/had my share of bugs in several languages after forgetting to add a specific break.
Now, the goto thing already mentioned gives me the shivers. Imagine the exploitability by some 1337 developer playing around with those goto's. For example:
switch(x) { case 1: doThis(); goto case 2; case 2: doThat(); goto case 1; // infinite loop! case 3: doOtherwise(); goto case 1; // yesss, yesss, shoot us in the footses };
-
Pretty much the only time you can get an non-cyclic infinite list of finite values is when you throw in a pure random number generator into the mix, at which point you're doing something so extremely wrong nothing will save whatever monstrosity was just coded.
There are other ways too, such as a sequence of two numbers (e.g., 0 and 1) where there is a monotonically-increasing interval between each 1 in the stream of 0 values. It's an infinite non-repeating sequence with only two member values and a simple generating rule.
1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0 ,0 ,0 1, …
-
I stand corrected, never thought of that one
-
Now, the goto thing already mentioned gives me the shivers. Imagine the exploitability by some 1337 developer playing around with those goto's.
How else are you going to implement a true state machine? Unfortunately, not all of them are simple to decompose into structured programs (or at least not without increasing the factor in other ways).
-
Just confirmed this actually works (my code was compiling):
#include <stdio.h> #define YIELD_START(pos) switch(pos) case 0: #define YIELD(pos,i,ret) (pos)=(i); return (ret); case (i): struct status { int pos; int i; }; int generate(struct status *s) { YIELD_START(s->pos) { for( s->i = 0 ; s->i < 10 ; s->i++ ) { YIELD(s->pos, 1, s->i); YIELD(s->pos, 2, s->i); } } return -1; } int main(int argc, char * argv[]) { struct status s; int r; s.pos = 0; s.i = 0; while( (r = generate(&s)) >= 0 ) { printf( "%d\n", r ); } return 0; }
-
I wonder if you could use
__LINE__
to make that code less magic-number ridden.
-
That should work. Means you can't yield twice on one line, but that seems acceptable. The compiler (or at least
gcc
) will complain if you use a label twice, but that error ends up nasty with the defines.Any ideas to get rid of the
pos
argument toYIELD
without putting the opening brace inYIELD_START
? Though you could probably get away with hardcodings->pos
even if you use this multiple times.
-
In C99 mode you can use these:
#define YIELD_START(pos) for(int * __pos = &(pos); __pos; __pos = NULL) switch(pos) case 0: #define YIELD(ret) *__pos=__LINE__; return (ret); case __LINE__:
-
I don't know, I've seen/had my share of bugs in several languages after forgetting to add a specific break.
I never said fall-through it a good idea. I just said that if the language doesn't support fall-through, it shouldn't require explicit break after every case - it's just unnecessary clutter. If my car has automatic emergency brake, I shouldn't have to release it every time I get moving after stop.Now, the goto thing already mentioned gives me the shivers.
Duh! It's goto after all!How else are you going to implement a true state machine?
Have aState
interface implemented by every state class, one for each state I want. We don't have to implement '60s design patterns using '60s tools in 2015.
-
Have a State interface implemented by every state class, one for each state I want. We don't have to implement '60s design patterns using '60s tools in 2015.
This is one of those cases where I can't figure out why people stick with the old ways. You often see performance and bloat as arguments against object oriented programming, but the opposite is true here. A switch statement is implemented as a series of comparisons and jumps, while an interface-based design is implemented as just a pointer in a vtable.
-
On most architectures, in most cases, series of comparisons and jumps will still be faster than vtable - remember that the former can be compiled to jump table, and the latter has potential of two cache misses and is harder to optimize for the compiler. But the difference is almost always negligible.
-
A switch statement is implemented as a series of comparisons and jumps
That's one way they might be done. There are others.
-
If you're going to reuse a language construct like that you can't go changing the basic functionality without some clear warning for people who are used to the other behavior. Belt and suspenders is the only thing that makes sense here.
If they hadn't forced you to add those breaks, how much more annoying would their (very sensible) decision not to allow fall-through have been to you?
-
If you're going to reuse a language construct like that you can't go changing the basic functionality without some clear warning for people who are used to the other behavior.
Java turned the C world upside down with their reference semantics for variables.string a = "dupa"; string b = a;
does totally different thing in Java and C++, yet I never heard a complaint about Java for this particular thing being confusing.Of course, there are some people who will try to fall-through in C#, but:
- they will immediately notice their code is not working as intended;
- should we really design programming languages to cater idiots?
If they hadn't forced you to add those breaks, how much more annoying would their (very sensible) decision not to allow fall-through have been to you?
As I repeated over and over again already - disallowing fall-through was a great decision. My only complaint is about unnecessary verbosity due to break statements which are basically implied there, so the only result is decreased signal-to-noise ratio. Remember that main reason why people hate Java is extreme verbosity.
-
Except that's not the type of mistake an idiot makes, that's the type of mistake anyone could make, coming to c# from another language, or even working on multiple projects in different languages at the same time—sometimes the details slip your mind. And your assertion that they would immediately notice seems a bit naive: if they've already forgotten which semantics they're dealing with once, do you think they're gonna specifically test that behavior? Next do you want them to test that only one branch of their if-else gets taken?
Remember that main reason why people hate Java is extreme verbosity
Should we really design programming languages to cater idiots?
-
Except that's not the type of mistake an idiot makes
Writing code in language A that doesn't make sense here because it's how he does things in language B? Yep, definitely not an idiot's way of doing things.that's the type of mistake anyone could make, coming to c# from another language
Only the subset of people who don't know how this particular language feature works. And the solution is RTFM.or even working on multiple projects in different languages at the same time
This excuse makes as much sense as excusing an American driver in Germany for making the right turn on red light and causing accident.sometimes the details slip your mind
The detail that slips my mind most often is to writebreak
after eachcase
. Every time I make case fall-through in C or C++, I never meant to do it. In C#, this results in compilation error. In C# after change I propose, my code would result in correct behavior.And your assertion that they would immediately notice seems a bit naive: if they've already forgotten which semantics they're dealing with once, do you think they're gonna specifically test that behavior?
It's 2015. Anyone who doesn't write unit tests for all code paths is an idiot. Or works in shitty corporation under idiot management, in which case the semantics of switch is the least of his problems.Next do you want them to test that only one branch of their if-else gets taken?
No - I want to them to test their code for every kind of input, exercising every possible code path (except the impossible ones).@Gaska said:
Remember that main reason why people hate Java is extreme verbosity.
Should we really design programming languages to cater idiots?
What's idiotic in writing short code?
-
This excuse makes as much sense as excusing an American driver in Germany for making the right turn on red light and causing accident
It's a mistake anyone could make. It's fucked up that Germany has wacky-ass road rules that don't follow the conventions.
But if cars could be designed to ensure that every maneuver you made was both intended and legal, and you were here calling that feature pointless noise, I would be calling you a jerk for that just as I am now.
In C# after change I propose, my code would result in correct behavior.
Compiler already holds your dick when you piss, now you want it to shake you off before tucking it in.
@Gaska said:What's idiotic in writing short code?
Redefining existing language constructs to mean the opposite of what they used to is idiotic. C# designers did the best they could with what they had.
-
It's a mistake anyone could make.
An European wouldn't.It's fucked up that Germany has wacky-ass road rules that don't follow the conventions.
What conventions? Nothing in the Vienna Convention says anything about right turns on red light, and I'm not aware of any other international convention regulating traffic laws.But if cars could be designed to ensure that every maneuver you made was both intended and legal, and you were here calling that feature pointless noise, I would be calling you a jerk for that just as I am now.
If I couldn't drive 70km/h on a four-lane road with 50km/h limit, I would bitch about it.Compiler already holds your dick when you piss, now you want it to shake you off before tucking it in.
It's more like compiler fusing my penis until I sit down on the toilet to make sure I don't piss over the floor.Redefining existing language constructs to mean the opposite of what they used to is idiotic.
What is opposite to what, exactly? And who's redefining anything? Additive change isn't redefining, and implicit break is technically speaking an additive change to a language.C# designers did the best they could with what they had.
That's not conclusion, that's premise. A premise I consider false.
-
@Buddy said:
C# designers did the best they could with what they had.
That's not conclusion, that's premise. A premise I consider false.
They did the best they were allowed to do by management.
This is neither a conclusion nor a premise but a supposition based on my experience (and what many other Internet inhabitants write what is their experience).
-
They did the best they were allowed to do by management.
If shitty feature was ordered by the management, does it make the feature any less shitty?
-
If shitty feature was ordered by the management, does it make the feature any less shitty?
Of course. Enterprisey is the opposite of shitty.
-
And who's redefining anything? Additive change isn't redefining, and implicit break is technically speaking an additive change to a language
You have a weird understanding of words.
-
If I couldn't drive 70km/h on a four-lane road with 50km/h limit, I would bitch about it.
That's because you're a jerk.
-
You have a weird understanding of words.
OK, let's tackle it from another side. You say redefining existing language constructs is bad. Yet C# has already drifted off the established conventions by introducing properties, which is basically method calls disguised as variable accesses. If I sayi = 0
, I would expect a variablei
to have a value0
, not repainting the whole window. Do you think it's OK to introduce such counterintuitive feature? Do you think it's less of redefining existing language constructs than implicit break in switches?That's because you're a jerk.
I don't understand how driving at the speed matching the road condition is being a jerk. If anything, it's the local government that enforce crawl speed on a road that's perfectly appropriate for 100km/h.
-
If I say i = 0, I would expect a variable i to have a value 0, not repainting the whole window. Do you think it's OK to introduce such counterintuitive feature?
At large, I agree with your first paragraph, but properties have been there in VBA as long as I know that, and I suppose this is something C# has taken from the BASIC side. (There are some other features/"features" too, like the already mentioned elimination of switch fallthrough.)
Although in this case, I think that properties can be really useful, like the background color of a control element which needs some coding to visually reflect the change of the property. That programmers can abuse property methods is a different matter. (See the discussion about foreach and IEnumerable.)
I don't understand how driving at the speed matching the road condition is being a jerk. If anything, it's the local government that enforce crawl speed on a road that's perfectly appropriate for 100km/h.
In general, I agree with this, although I tend to obey the authorities (not quite?) as far as feasible. This is another problem I'd like to discuss (am not sure whether a new topic would be advisable).