Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the worst gotcha in C# or .NET? [closed]

Tags:

c#

.net

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


Re-throwing exceptions

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); 
}