private int myVar;
public int MyVar
{
get { return MyVar; }
}
Blammo. Your app crashes with no stack trace. Happens all the time.
(Notice capital MyVar
instead of lowercase myVar
in the getter.)
Type.GetType
The one which I've seen bite lots of people is Type.GetType(string)
. They wonder why it works for types in their own assembly, and some types like System.String
, but not System.Windows.Forms.Form
. The answer is that it only looks in the current assembly and in mscorlib
.
Anonymous methods
C# 2.0 introduced anonymous methods, leading to nasty situations like this:
using System;
using System.Threading;
class Test
{
static void Main()
{
for (int i=0; i < 10; i++)
{
ThreadStart ts = delegate { Console.WriteLine(i); };
new Thread(ts).Start();
}
}
}
What will that print out? Well, it entirely depends on the scheduling. It will print 10 numbers, but it probably won't print 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 which is what you might expect. The problem is that it's the i
variable which has been captured, not its value at the point of the creation of the delegate. This can be solved easily with an extra local variable of the right scope:
using System;
using System.Threading;
class Test
{
static void Main()
{
for (int i=0; i < 10; i++)
{
int copy = i;
ThreadStart ts = delegate { Console.WriteLine(copy); };
new Thread(ts).Start();
}
}
}
Deferred execution of iterator blocks
This "poor man's unit test" doesn't pass - why not?
using System;
using System.Collections.Generic;
using System.Diagnostics;
class Test
{
static IEnumerable<char> CapitalLetters(string input)
{
if (input == null)
{
throw new ArgumentNullException(input);
}
foreach (char c in input)
{
yield return char.ToUpper(c);
}
}
static void Main()
{
// Test that null input is handled correctly
try
{
CapitalLetters(null);
Console.WriteLine("An exception should have been thrown!");
}
catch (ArgumentNullException)
{
// Expected
}
}
}
The answer is that the code within the source of the CapitalLetters
code doesn't get executed until the iterator's MoveNext()
method is first called.
I've got some other oddities on my brainteasers page.
The Heisenberg Watch Window
This can bite you badly if you're doing load-on-demand stuff, like this:
private MyClass _myObj;
public MyClass MyObj {
get {
if (_myObj == null)
_myObj = CreateMyObj(); // some other code to create my object
return _myObj;
}
}
Now let's say you have some code elsewhere using this:
// blah
// blah
MyObj.DoStuff(); // Line 3
// blah
Now you want to debug your CreateMyObj()
method. So you put a breakpoint on Line 3 above, with intention to step into the code. Just for good measure, you also put a breakpoint on the line above that says _myObj = CreateMyObj();
, and even a breakpoint inside CreateMyObj()
itself.
The code hits your breakpoint on Line 3. You step into the code. You expect to enter the conditional code, because _myObj
is obviously null, right? Uh... so... why did it skip the condition and go straight to return _myObj
?! You hover your mouse over _myObj... and indeed, it does have a value! How did THAT happen?!
The answer is that your IDE caused it to get a value, because you have a "watch" window open - especially the "Autos" watch window, which displays the values of all variables/properties relevant to the current or previous line of execution. When you hit your breakpoint on Line 3, the watch window decided that you would be interested to know the value of MyObj
- so behind the scenes, ignoring any of your breakpoints, it went and calculated the value of MyObj
for you - including the call to CreateMyObj()
that sets the value of _myObj!
That's why I call this the Heisenberg Watch Window - you cannot observe the value without affecting it... :)
GOTCHA!
Edit - I feel @ChristianHayter's comment deserves inclusion in the main answer, because it looks like an effective workaround for this issue. So anytime you have a lazy-loaded property...
Decorate your property with [DebuggerBrowsable(DebuggerBrowsableState.Never)] or [DebuggerDisplay("<loaded on demand>")]. – Christian Hayter
A gotcha that gets lots of new developers, is the re-throw exception semantics.
Lots of time I see code like the following
catch(Exception e)
{
// Do stuff
throw e;
}
The problem is that it wipes the stack trace and makes diagnosing issues much harder, cause you can not track where the exception originated.
The correct code is either the throw statement with no args:
catch(Exception)
{
throw;
}
Or wrapping the exception in another one, and using inner exception to get the original stack trace:
catch(Exception e)
{
// Do stuff
throw new MySpecialException(e);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With