Make it work, make it clean, make it SOLID, THEN make it work as fast as it needs to work.
That should be the normal order of things. Your very first priority is to make something that will pass the acceptance tests which shake out of the requirements. This is your first priority because it is your client's first priority; meeting the functional requirements within the development deadlines. The next priority is to write clean, readable code that is easy to understand and can thus be maintained by your posterity without any WTFs when that becomes necessary (it's almost never a question of "if"; you or someone after you WILL have to go back in and change/fix something). The third priority is to make the code adhere to the SOLID methodology (or GRASP if you prefer), which puts code into modular, reusable, replaceable chunks that again aids maintenance (not only can they understand what you did and why, but there are clean lines along which I can surgically remove and replace pieces of code). The last priority is performance; if code is important enough that it has to conform to performance specs, it's almost certainly important enough to be made correct, clean and SOLID first.
Echoing Christopher (and Donald Knuth), "premature optimization is the root of all evil". In addition, the kind of optimizations you are considering are both minor (a reference to your new object will be created on the stack whether you give it a name in source code or not) and of a type that may not cause any difference in compiled IL. Variable names aren't carried forward into the IL, so since you're declaring the variable right before its first (and probably only) use, I would bet some beer money that the IL is identical between your two examples. So, your coworker is 100% right; you are looking in the wrong place if you're looking at named variable vs inline instantiation for something to optimize.
Micro-optimizations in .NET are almost never worth it (I'm talking about 99.99% of cases). In C/C++, maybe, IF you know what you're doing. When working in a .NET environment, you already are far enough away from the metal of the hardware that there is significant overhead in code execution. So, given that you are already in an environment that indicates you've given up on blistering speed and are instead looking to write "correct" code, if something in a .NET environment really isn't working fast enough, either its complexity is too high, or you should be considering parallelizing it. Here are some basic pointers to follow for optimization; I guarantee you your productivity in optimization (speed gained for time spent) will skyrocket:
- Changing the function shape matters more than changing the coefficients - WRT Big-Oh complexity, you can reduce the number of steps that must be executed in an N2 algorithm by half, and you still have a quadratic-complexity algorithm even though it executes in half the time it used to. If that's the lower bound of complexity for this type of problem, so be it, but if there's an NlogN, linear or logarithmic solution to the same problem, you will gain more by switching algorithms to reduce complexity than by optimizing the one you have.
- Just because you can't see the complexity doesn't mean it isn't costing you - Many of the most elegant one-liners in the word perform terribly. Linq is a great library because it simplifies code, but unlike a SQL engine, the .Net compiler won't try to find the most efficient way of executing your query. You have to know what will happen when you use a method, and thus why a method might be faster if placed earlier (or later) in the chain, while producing the same results.
- OTOH, there's almost always a tradeoff between source complexity and runtime complexity - SelectionSort is very easy to implement; you could probably do it in 10LOC or less. MergeSort is a bit more complex, Quicksort more so, and RadixSort even more so. But, as the algorithms increase in coding complexity (and thus "up-front" development time), they decrease in runtime complexity; MergeSort and QuickSort are NlogN, and RadixSort is generally considered linear (technically it's NlogM where M is the largest number in N).
- Break fast - If there is a check that can be made inexpensively that is significantly likely to be true and means that you can move on, make that check first. If your algorithm, for instance, only cares about numbers that end in 1, 2, or 3, the most likely case (given completely random data) is a number that ends in some other digit, so test that the number does NOT end in 1, 2, or 3, before making any checks to see whether the number ends in 1, 2, or 3. If a piece of logic requires A&B, and P(A)=0.9 while P(B)=0.1, then check B first, unless if !A then !B (like
if(myObject != null && myObject.someProperty == 1)), or B takes more than 9 times longer than A to evaluate (
if(myObject != null && some10SecondMethodReturningBool())).
- Don't ask any question that you already know the answer to - If you have a series of "fall-through" conditions, and one or more of those conditions are dependent upon a simpler condition that must also be checked, never check both of these independently. For example, if you have a check that requires A, and a check that requires A && B, you should check A, and if true you should check B. If !A, then !A&&B, so don't even bother.
- The more times you do something, the more you should pay attention to how it's done - This is a common theme in development, on many levels; in a general development sense, "if a common task is time-consuming or fiddly, keep doing it until you're both frustrated and knowledgeable enough to come up with a better way". In code terms, the more times an inefficient algorithm is run, the more you'll gain in overall performance by optimizing it. There are profiling tools that can take a binary assembly and its debug symbols and show you, after running through some use cases, what lines of code were run the most. Those lines, and the lines that run those lines, are what you should pay the most attention to, because any increase in efficiency you achieve there will be multiplied.
- A more complex algorithm looks like a less complex algorithm if you throw enough hardware at it. There are some times where you just have to realize that your algorithm is approaching the technical limits of the system (or the part of it) that you're running it on; from that point if it needs to be faster, you'll gain more by simply running it on better hardware. This also applies to parallelization; an N2-complexity algorithm, when run on N cores, looks linear. So, if you're sure you've hit the lower complexity bound for the type of algorithm you are writing, look for ways to "divide and conquer".
- It's fast when it's fast enough - Unless you're hand-packing assembly to target a particular chip, there's always something to be gained. However, unless you WANT to be hand-packing assembly, you must always keep in mind what the client would call "good enough". Again, "premature optimization is the root of all evil"; when your client calls it fast enough, you're done until he doesn't think it's fast enough anymore.