On the last project I was on, there were a lot of ex-Java guys who were used to working with IntelliJ and Eclipse, and who derived endless pleasure from ridiculing Visual Studio's comparative shortcomings. They renamed it 'Visual Retardio' ('Visual Subbuteo' came a close second). When Jetbrains started the ReSharper EAP earlier this year we all jumped on board in a flash, and stuck through months of pain and suffering (it was nicknamed 'ReBlunter' for a time) until finally the finished product was released.
Then Visual Studio 2005 went beta, and I was hoping that Microsoft would now have an IDE that was competetive with the best Java IDEs, and that would give us all the things we had become used to with ReSharper.
After using VS.Net 2005 for a few weeks, and having used VS.Net 2003 with ReSharper for the last six months or so, I have come to realise the following:
I much prefer VS.Net 2003 + ReSharper over VS.Net 2005, for the following reasons:
- ReSharper's refactoring menu is context sensitive, and will only present those which are relevant to the current cursor location. 2005 just gives you all of them, then throws an error when you try and use one in the wrong place.
- ReSharper has Introduce variable. I use this all the time.
- ReSharper has a sweet inline rename for local variables and parameters, and will even suggest names for you based on the type.
- ReSharper will colour fields, local variables and methods.
- It will colour unused or unreachable code.
- It will highlight all usages of a field/variable/method within a file.
- Type this into any method in 2005:
What happens? Very little. Try it with ReSharper and it will colour 'WroteLine' in red, and present you with a drop-down list of all the valid Console methods (it will even narrow the list to the best matches).
4) Code Navigation
- 2005 now has a usable Goto Definition, but you can't navigate to sub or parent types.
- 2005 now has Find References, but it doesn't find references. It finds symbols matching the currently selected element, which is a very different thing. If I'm looking for references of a method, I don't want to be presented with a list of similar-looking methods on other classes, and I don't want the method I just selected to be present in that list. It's just broken.
- Guess what? Both these features really work in ReSharper. And it has decent 'Find Type' and 'Find Method' feature, which narrows the list as you type.
5) Quick Fixes / Smart Tags
- I'm no big-city usability expert <gasp!>, but to me, Smart Tags have a big failing: they require three steps to select an option via the keyboard. First, you use a hot-key to get the menu up. Then, you move the cursor down to the option you want, and then you hit return. This wouldn't be so bad, but if there's only one option in the list, you still have to navigate to it! ReSharper's Quick Fixes automatically select the first or only option in the list, and in some cases (if you use a type without importing it with a 'using' statement, for example) it will bring up a tooltip with the relevant option, requiring only one key press to activate it.
- ReSharper will give you a list of types which haven't yet been imported. When you select one, it will complete your code and add the 'using' statement for you. It will also auto-complete variable assignments, including brackets and a semi-colon if required.
- ReSharper's 'Extend Selection' feature is quite hard to explain, but very useful. Essentially it's like ctrl-click, but it selects outwards from the current cursor position, and is scope-aware (eg it will select the name of a method call, then the call + its parameters, then anything enclosing that, then anything enclosing that, right up to the file level).
- Duplicate Line - like [ctrl-c, cursor down, ctrl-v], but all in one go.
I really could go on, but this sounds too much like an advertisement already. Suffice to say that I am disappointed by the first beta of 2005, and I hope that Microsoft can improve things a little by the time they release. I am looking forward to the day when Visual Studio really understands code, rather than being a glorified text editor.
Anyone who has passed a managed delegate as a parameter to an unmanaged function will be familiar with the oh-dear-the-GC-just-pulled-the-rug-from-under-me NullReferenceException you get if you don't keep save a reference to that delegate. It's hard to debug, but once you are aware of the problem you tend not to make the same mistake again. Or. So. I. Thought.
So I'm merrily hacking away with .Net 2.0, trying out the new flashy bits like generics and anonymous methods, when I tried out the new type-inference features in C#. Essentially this means that C# creates the delegate for you, and code like this:
button.Click += new ClickEventHandler(OnClick);
can be simplified like this:
button.Click += OnClick;
which is much nicer. It also means you can pass delegates to, you guessed it, unmanaged code without explicitly declaring a new delegate instance, and then spend a happy few hours debugging a stack-less NullReferenceException, safe in the knowledge that the problem can't be related to the delegate being garbage collected, because you'd never make that mistake again. In my defence, I was clearly drunk on syntactic sugar.
Which leads me to the second whammy: in 2.0, you don't always get a NullReferenceException in this situation. Sometimes, you get an AccessViolationException, which is a new type meant to help distinguish between null references and other types of AVs. Unfortunately, the CLR seems to be inconsistent about which one it chooses to throw (I was getting NRE on some runs and AVE on others) and led me to investigate the wrong flavour of bug.
I missed one off the Programming by Superstition list:
DataSets store their data internally as XML.
Microsoft said so!
When ADO.Net was announced, there was a lot of marketing guff about how it was "based around" XML, and how XML was a "first-class citizen". After all, it was the year 2000, and no CxO worth their salt was going to buy into a solution that wasn't "fully XML enabled". In fact, there is at least one article on MSDN which refers to DataSets being "natively an XML structure", so perhaps Microsoft believed their own hype.
DataSets aren't really the key structure here - they don't do much more than group DataTables together. In turn, DataTables group DataColumns together, and DataRows act as a "horizontal slice" into a group of DataColumns. Going one level further, a DataColumn is just a wrapper around one of a number of classes inheriting from DataStorage (BooleanStorage, ByteStorage, CharStorage and so on), and those are just strongly-typed arrays with a bit of collection-management logic thrown on top of them. So, no XML. Sorry.
(And just for the record, they would have been feckin stupid to have used XML anyway).
Myths and half-truths around .Net and C# best-practices crop up frequently in discussion forums, with rationales ranging from utterly believable to indefensible. I thought I would catalogue the ones I see most frequently. I realise I am leaving myself open to ridicule by prefixing my 'corrections' with 'Truth', but someone has to make a stand :-) Feel free to correct me.
Always set your objects to null (or Nothing) when you're done with them.
The garbage collector won't be able to free an object if any another object is referencing it.
The garbage collector can free any object that is unreachable from any of the various heap roots. If object A references object B, and object A is unreachable, object B can be released if it has no other references from reachable objects.
StringBuilder.Append() is always faster than String.Concat().
String operations always create new string objects, whereas StringBuilder uses internal magic to avoid expensive allocations.
Using a StringBuilder is sometimes (even usually) faster than using simple string concatenation, but there is a performance cutover point. Various people have found this to be around the 5 - 10 concatenations mark. The bottom line being that replacing this:
string fullName = firstName + " " + lastName;
StringBuilder builder = new StringBuilder();
string fullName = builder.ToString();is rather pointless. Remember that StringBuilder is itself reference type, and has the associated allocation/deallocation costs.
Strings are passed to, and returned from, methods by-value.
Strings are immutable, therefore they must have value type semantics, therefore must be copied by value.
Strings have certain value-type semantics (namely, immutability), but are otherwise heap-based reference types, and references to them can indeed be passed or copied. Passing a string reference to or from a method does not copy the underlying string.
Dispose() releases an object's memory.
Well, it's called 'Dispose' isn't it?
Dispose() is merely a convenient place for a class' developer to place any resource clean-up code, in the hope that clients will call it. Dispose does not free the managed memory allocated for an object. The only thing that can do that is the garbage collector; there is no way to deterministically (or manually) release memory.
Keeping ADO.Net connection objects open for the duration of a [Page/Control/1000-line method]'s lifetime is more efficient than repeatedly opening and closing it around each operation.
Connecting to a database is expensive, so try to avoid doing it too much.
If your managed provider allows connection pooling, and if pooling is enabled (it is, by default, for the managed SQL Server provider at least), calling Close() on the connection object does not simply close the underlying connection. It merely releases that connection back to the pool where it can be associated with another managed connection object at some time in the future. This is a Good Thing, especially in multi-threaded environments such as ASP.Net, because physical connections are rare resources and should not be coveted. (By the same token, calling Open() on a managed connection when a pooled physical connection is available will simply associate the former with the latter).
By keeping a managed connection in an open state, you are preventing objects in other threads from getting hold of them, leading to potential bottlenecks.
Int32 and int are not the same thing (ditto float/Short, long/Int64, string/String, etc.).
They're spelled differently.
The C# simple type keywords are interchangeable with the equivalent BCL types; they are aliases.