|
The REALLY big mistake is that the C# designers carried forward the old C way of reporting something non-numeric as if it were a numeric. IT IS NOT!
The value of comparing A with B is either "A is less", "They are equal", or "B is less", NOT -1, 0 or 1. C# did abandon pointers as integers - "if (pointer)" is not valid; you must test "if (pointer != null)". They should have completed the job!
Every now and then I get so frustrated over this that I write a thin skin for the comparisons, casting those inappropriate integers into an enum. But C# doesn't really treat enums as a proper type, more as synonyms for integers, so it really doesn't do it; it just reduces my frustration to a managable level.
|
|
|
|
|
The problem with that is they may not be 1, 0 or -1.
Any positive value and 1 are going to have to be treated the same, and the same goes for the negative values - they're all -1, basically.
But other than that, yeah.
Although hate enums, because .NET made them slow. I still use them, but they make me frustrated.
So usually in my classes where I don't want to burn extra clocks like my pull parsers I use an int to keep state, and cast it to an enum before the user of my code touches is.
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
honey the codewitch wrote: Although hate enums, because .NET made them slow. I still use them, but they make me frustrated. The very first compiler I dug into was the Pascal P4 compiler - those who think "open source" is something that came with Linux are completely wrong. Pascal provides enums as first class types, not something derived from integer.
The compiler source showed very clearly how the compiler treats enums just like integers; it just doesn't mix the two types up, it doensn't allow you to use them interchangably. It is like having intTypeA and intTypeB which are 100% incompatible. If you do casting to (or from) int, it is a pure compile-time thing: It shortcuts the error handling reporting that the types are incompatible. There is nothing that causes more instructions to be executed when you use enums rather than int - not even when you do casting. Why would there be? Why should .net make them slower?
If you have full enum implementation (like that of Pascal) and make more use of it, then there may of course be a few more instructions generated. E.g. if you have a 12-value enum from janurary to december, and define an array with indexes from april to august, then the runtime code must skew the actual index values so that an "april" index is mapped to the base adress of the array, not three elements higher. Index values must be checked against the array declaration: january to march and september to december must generate an exception. But that is extended functionality - if you want that with integer indexes, and the same checking, you would generate a lot more code writing the index scewing and testing as explicit C statements.
Maybe the current C# .net compiler is not doing things "properly" - similar to that Pascal compiler written in the early 1970s. I guess it could. I see no reason why it should be able to, nothing in semantics of C# "semi-enums" making it more difficult that Pascal's full enum implemnentation.
|
|
|
|
|
It depends on what you do with them, but casting them back and forth to int requires a CLI check, i think maybe for invalid values.
Ints don't require that.
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
Casting is not something you do very often - unless you continue to think of enums as just names for ints, so you continue to mix the two types up. So it shouldn't be essential (or even noticable) to system performance.
In many cases, the compiler can suppress the runtime check, e.g. for int literals, or when a simple flow analysis reveals that an int variable couldn't possibly be outside the enum range (or most certainly would be outside, in which case the compiler should barf).
For enum-to-int casts, there should be very little need for runtime checks - very few systems define more than 32K values for one enum type, and very little code nowadays use ints of less than 16 bits. Especially: In contexts where 8 bit ints are relevant, you very rarely see huge enum definitions with more than 128 alternatives (or 256 for uint8).
If you declare enums by forcing the internal representation to be given by the bit pattern of some int value, then you show that you do not recognize enums as a distinct type. Forcing the internal representation is as bad for enums as it would be to force a pointer or float to a specific bit pattern given by the representation of a specific integer literal. You shouldn't do that. Even assuming that enum values form a dense sequence from 0000 and upwards is on the edge - they are not ints, and you cannot assume any given similarity in int and enum implementation. Really, int/enum casts are as meaningless as int/pointer casts; we have casts only because lots of C programmers can't stop thinking of them as "just a little different ints".
Even for ints, the compiler should generate code that verifies that e.g. an int32 cast to an int16 is within the int16 range. Maybe the instruction set provides some harware support, creating an interrupt if not. Support may be available even for enum use: The last machine I programmed in assembly had a four operand instruction "LoadIndex register, value, min, max": If "value" was not in the range from min to max, an "illegal index" interrupt was generated. The Pascal compiler used this instruction for int-to-enum casts, specifying the first and last permitted enum value. (In Pascal, it is not given that "min" is zero; e.g. if an array is indexed from may to september.) I haven't spent time on learning the .net "instruction set", and don't know if it has something similar. But since it does index checks, I'd exepect it to.
|
|
|
|
|
Member 7989122 wrote: In many cases, the compiler can suppress the runtime check, e.g. for int literals,
Plenty of developers overestimate the compilers they provide with .NET. They don't typically do optimizations like that. (Although in .NET you can explicitly turn overflow checks on and off on a per cast basis in your code)
Experience has shown me time and again, when it comes to .NET, if there's any doubt about whether or not the compiler will optimize something, no matter how obvious, assume it won't.
You're far more likely to be right than wrong that way.
Spend enough time decompiling .NET asms and you learn the hard way to optimize your own code.
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
dotNET is neither a compiler, an instruction set nor a machine architecture. (For the last one: Contrary to e.g. P4 or JVM.) It is like the specification of the interface in the gcc compiler suite between the (programming language dependent) front end and the (cpu architecture dependent) back end. "Like" is not in a "somewhat simillar to, in a rough sense", but rather like "an alternative competing directly with the gcc intermediate format for solving exactly the same set of problems". The backend exists in .net exactly as in gcc: CPU architecture dependent code is generated when the code is first loaded into the machine, and cached for later use.
Do you ever "blame" the intermedate gcc format for front ends that doesn't optimize the code much? That's what you do with dotNET. Put the blame where it deserves to be put.
I haven't studied the source code of a single compiler for the intermediate code of neither gcc nor dotNET. Maybe they don't do even the very simplest optimizations. Why not? Most techniques have been known for more than fifty years. I may suggest that they make a tradeoff: Compile time is far more essential with an interactive IDE than in the days when you submitted a card deck for compilation and picked up the compiler listing hours earlier. So we can't spend time on optimizations while the developer is impatiently twiddeling their thumbs waiting for the compilation to complete. Execution time is far less essential today - CPUs are fast enough! Optimizing for space is almost meaningless: Adding another 16 GiByte of RAM costs almost nothing, so why delay compilation to avoid that?
To some degree, they are right. And also: Modern pipelined CPUs reduces drastically the benefit of shaving off a few instructions from a linear sequence, compared to a hardcoded RISC CPU where every instruction (ideally) requires one clock cycle. Some of the old optimization techniques can safely be left out, as they have no measurable effect at all, given today's hardware.
Example, although not from compilers: Remember the "interleaving factor" when formatting DOS disks? If logical disk blocks 0, 1, 2... were physically laid out at sectors 0, 2, 4..., then you could read an entire track in only two disk revolutions. If you laid them out at sectors 0, 1, 2..., after reading sector 0, sector 1 passed the disk head while the controller was still stuffing away the data from sector 0. So it had to wait until sector 1 came around next time, a full revolution later. Reading an entire track, when interleaving was not applied, could require as many disk revolutions as the track had sectors. It must be 25+ years since the retirement of the last disk benefiting from interleaving.
Actually, Univac 1100-series compilers used a similar interleaving for data: When a word is read from memory, that entire row must be refreshed before it can be read again. So, consecutive locations were allocated cyclically in a set of rows: If you traverse an array sequentially (which is quite common), you skip from row to row, and when you cycle back to the first one, it has had plenty of time to refresh. With three cache levels in the CPU, and integral refresh logic, such optimization in the compiler is meaningless.
I did quite a lot of timing, from a programer's point of view, when I was working for a company making their own CPUs (this was in the "supermini" era), talking directly with the CPU designers. That's when I first learned the unimportance of the length of linear sequences, the effects of pipelining and interrups invalidating the pipeline. I also saw them remove a handful "optimizing" hardware features: They turned out to have microscopic effect but made the CPU significantly more complex, making it difficult to achieve a general speedup.
A more recent example: I made a small Soduko-solver to illustrate backtracking for a colleague, counting how many times I try to place a digit, testing if it is viable. The loop contains 13 C# statements: 3 if-statements, 6 one-dim array indexing operations, 2 two-dim array indexing operations, one of two alternate function calls..., plus the for loop control. One iteration of this loop takes around 30 nanoseconds. How much below 30 ns could I squeeze it by hand optimizing those 13 statements? Not that much. The worst puzzle I have found, requiring more than 57 million test loop iterations, complete in 1.8 seconds; there is no hard "user level" requirement for further optimzation!
So: I rarely study the instruction sequence generated, claiming that "I could have removed four instructions from that linear sequence". Rather, I do timing at application level, to see the real improvements in execution speed. Often, they are not possible to measure. Sometimes they are negative. Sometimes I am tempted to look at the generated code to see how it can be possible to run those 13 sudoko-solver C# statements in 30 nanoseconds...
|
|
|
|
|
I didn't say it was. I'm well aware of .NET and what it is and isn't. I worked on VStudio Whidbey, FFS.
Member 7989122 wrote: Do you ever "blame" the intermedate gcc format for front ends that doesn't optimize the code much?
This isn't about blame. This is about stating a fact. None of the compilers shipped with any flavor of .NET that targets any flavor of the CLI optimizes very much, if at all. And when one writes code it pays to know what the compiler is actually doing with it, lest someone make some bad design decisions (like trying to use enums for state machine internals)
It's all punted to the JIT, and the JIT just doesn't optimize much.
You can wrap a response to that in as many words as you like, but it doesn't change the facts on the ground.
And it won't get me to start using enums in my state machine code internals.
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
If it ain't broke, fix it 'til it is.
Oh, hang on - save your ass, you say?
|
|
|
|
|
Don't return null . Throw an exception instead.
Removes need to null check everything.
Hopefully give a more meaningful error when a problem occurs.
|
|
|
|
|
Yikes! I'd hate to write code that way but to each their own.
Null is often as not, a valid value, and not even a symptom of some sort of error.
No way do I want to wrap my calls to a function that is expected to have null as one of its values with:
object result;
try {
result=Foo();
}
catch { result = null;}
more bugs, extra slow, and for no reason.
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
Different use cases.
I’m stuck debugging an app that is crashing because NULL is not a valid value.
A part of what makes programming fun (?), is the various ways to solve a particular problem.
|
|
|
|
|
ha!, i can understand your sentiment. I recently developed a caching JSON entity framework for accessing TMDb's REST API and all their JSON fields are marked optional, which means potential for nulls everywhere. It made errorhandling hell.
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
TrinityRaven wrote: Don't return null . Throw an exception instead. Sure, if it really is an execption.
But I don't want to handle, say, a person who has no middle name as an exception just because his middle name is null. Or a person without a spouse, or without children.
I can guess your reply: The middle name should be a zero lenght string, not null!
In some cases, a zero-size value may be conceptually correct. Far from always. There is a semantic difference between something being there, regardless of size, and something not being there. You easily end up with testing on nonzero size rather than null, which may in some contexts be both confusing and give more complex code. And it might require more data space.
I guess that you still accept null checks in loops and list traversals, as long as as no function calls are involved: "while (nextobject != null) {process it and determine the next object}" is perfectly fine ... until "determine the next object" becomes so complex that you factor it out as a function. By your rule, the while condition can be dropped; you will threat the end of the list as something exceptional that requires exception handling.
But it isn't "exceptional" to reach the end of a list in list traversal! If you do not process all elements but factor out the code that decides which elements to skip, that doesn't make the end of the list more exceptional.
I started learing programming when access to computer resources were scarce. Maybe that was one reason for why many of the first hand-ins were to be made in pseudocode: somewhat formalized English, but remote from coding syntax. Actually, if we got even close to a programming language syntax, the professor used his red pen: Why do you restrict it this way? Is there, or isn't there, a semantic difference between this kind of value and that kind? Is it appropriate to add #apples to #oranges here - you tell that there isn't?
I like pseudocode. It relieves you from language syntax, lets you describe the problem solution at a logical level. If I had it my way, every software design should include a documentation of the solution logic in a form of pseudocode completely removed from any programming language. It should be equally valid if it was decided to re-implement the C++ system i Fortran, or Visual Basic or Erlang or APL. Even if the system is never reimplemented in another language, I think that kind of documentation would improve code quality, by focusing on the problem solution rather on syntax details.
|
|
|
|
|
I didn't say don't use null . I said don't return null
NULL can be useful in a data structure, and to use your example in a Person of Name class having null for the middle name could be (I won't say "is") better that "NMN" (No Middle Name) or similar.
The question is what helps save [my] behind.
There are times when "yoda conditionals" make sense. There are use cases where they don't. I didn't specifically chime in on that discussion because I can see both sides and use (or not) depending on readability and what is being tested for.
Returning null, in my not so humble opinion, is a code smell. Using null in a data structure is not.
But ultimately, it depends on the team's (or single developer's) style and agreements. And do you accept the related overhead - null checks (or Elvis operator), or try ... catch.
|
|
|
|
|
TrinityRaven wrote: I didn't say don't use null . I said don't return null Yes, that's exactly what I pointed out in my loop example: You accept a loop to run until the next element is null unless determining the next element is so complex that it has been pulled out as a function.
If you do that, pull it out as a function, and follow your rule, then the function cannot return the next element the way the simpler inline code (with no function definition) did. The function would have to raise an exception when reaching the end of the list, and the call to the function would have to be wrapped in a try-catch, the exception handler would treat the exception as "ok, so then we set next element to null, so that the while check will terminate the loop", rather than simply accept a the next element as null from the function.
I find that to be an outright silly way of coding - and I don't think that you seriously suggest it. "don't return null" wasn't meant that absolutely; there are cases where communicating a null value to a calling function as something perfectly normal is ... perfectly normal. I say: That happens quite often. You say: OK, in some very special circumstances, like the one with "next object", you could accept it, as an exceptional case. - The question is where to draw the line. But the line is there.
I have seen code that tries to hide nulls by returning pseudo objects: If you ask for, say, a person's spouse, you never receive "null" or "none" or "void", but a person object that has a special identifier member like "no person". Testing for the returned person object being a person with a "no person" identifier is not more convenient by any criteria. You might forget to do that check, too, an reference attributes of this person object, that it doesn't have, because it is a "no person".
Finally: You make an absolute assumption that the called routine remembers to always define the return value to something non-null. I have had cases where the null check on the return value revealed errors in the called function, in a "graceful" way. If my programming style had been "You don't have to check for null returns, because functions do not return null", the error would have been caught much later.
Nowadays, we are using static code analysis tools that do a very thorough check on pointer use. If there is any chance whatsoever that a pointer is null or unassigned when dereferenced, you receive a warning. I have experienced flow paths with 20+ decision points, running through four levels of function calls, telling me that "What you did (or didn't do) there, at the start of the chain, might lead to the pointer you dereference at the end of the chain is null, if this and that and that condition is fulfilled". (And, if I make no use of the returned value, the analyzer is quiet - there is no danger.)
|
|
|
|
|
"Registered Nurse" to remember \r\n
|
|
|
|
|
My Mantra: "I'm too old for this ***t"
|
|
|
|
|
I'd be curious to see an expansion of "this ***t".
It might very well have great overlaps with my list. I know very well the feelings that you are expressing.
|
|
|
|
|
This catchy phrase was uttered by Roger Murtaugh (Danny Glover) in the original Lethal Weapon movie and then carried to the rest of that franchise.
|
|
|
|
|
|
i hear you
Usually it's my code that I want to be in a hurry. =) Go! Compute that LALR(1) table! Factor that grammar!
When I was growin' up, I was the smartest kid I knew. Maybe that was just because I didn't know that many kids. All I know is now I feel the opposite.
|
|
|
|
|
Hmmmm... "Hurry", maybe that's a name I should consider for my new car. "I'm in Hurry, don't hinder me".
(My present one is a red Ford, so I call it Robert.)
|
|
|
|
|
Mostly I just follow the Babylon 5 mantra. I also often catalog the stupidity I'm about to do prior to doing it.
|
|
|
|
|
Write once, read multiple
eg, in SQL - if you're thinking "a" is a valid alias for Account table then think again. Account is not a long word. Make it meaningful. If you have AccountProductMemberElephant table then APME is not a valid alias. These of kind of aliases result in the future readers having to reference back to the table to find what that means. Either don't alias or if you have some meaningless prefix to the table (eg company name) alias to remove that
Just remember you're only writing it once. It'll be read 100s of times!
Or another favourite and one not everybody agrees with
If it's not readable it's wrong!
|
|
|
|
|