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

Viewed 39428

I was recently working with a DateTime object, and wrote something like this:

DateTime dt = DateTime.Now;
dt.AddDays(1);
return dt; // still today's date! WTF?

The intellisense documentation for AddDays() says it adds a day to the date, which it doesn't - it actually returns a date with a day added to it, so you have to write it like:

DateTime dt = DateTime.Now;
dt = dt.AddDays(1);
return dt; // tomorrow's date

This one has bitten me a number of times before, so I thought it would be useful to catalog the worst C# gotchas.

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

Here's another time one that gets me:

static void PrintHowLong(DateTime a, DateTime b)
{
    TimeSpan span = a - b;
    Console.WriteLine(span.Seconds);        // WRONG!
    Console.WriteLine(span.TotalSeconds);   // RIGHT!
}

TimeSpan.Seconds is the seconds portion of the timespan (2 minutes and 0 seconds has a seconds value of 0).

TimeSpan.TotalSeconds is the entire timespan measured in seconds (2 minutes has a total seconds value of 120).

overloaded == operators and untyped containers (arraylists, datasets, etc.):

string my = "my ";
Debug.Assert(my+"string" == "my string"); //true

var a = new ArrayList();
a.Add(my+"string");
a.Add("my string");

// uses ==(object) instead of ==(string)
Debug.Assert(a[1] == "my string"); // true, due to interning magic
Debug.Assert(a[0] == "my string"); // false

Solutions?

  • always use string.Equals(a, b) when you are comparing string types

  • using generics like List<string> to ensure that both operands are strings.

If you count ASP.NET, I'd say the webforms lifecycle is a pretty big gotcha to me. I've spent countless hours debugging poorly written webforms code, just because a lot of developers just don't really understand when to use which event handler (me included, sadly).

DateTime.ToString("dd/MM/yyyy"); This will actually not always give you dd/MM/yyyy but instead it will take into account the regional settings and replace your date separator depending on where you are. So you might get dd-MM-yyyy or something alike.

The right way to do this is to use DateTime.ToString("dd'/'MM'/'yyyy");


DateTime.ToString("r") is supposed to convert to RFC1123, which uses GMT. GMT is within a fraction of a second from UTC, and yet the "r" format specifier does not convert to UTC, even if the DateTime in question is specified as Local.

This results in the following gotcha (varies depending on how far your local time is from UTC):

DateTime.Parse("Tue, 06 Sep 2011 16:35:12 GMT").ToString("r")
>              "Tue, 06 Sep 2011 17:35:12 GMT"

Whoops!

I saw this one posted the other day, and I think it is pretty obscure, and painful for those that don't know

int x = 0;
x = x++;
return x;

As that will return 0 and not 1 as most would expect

I'm a bit late to this party, but I have two gotchas that have both bitten me recently:

DateTime resolution

The Ticks property measures time in 10-millionths of a second (100 nanosecond blocks), however the resolution is not 100 nanoseconds, it's about 15ms.

This code:

long now = DateTime.Now.Ticks;
for (int i = 0; i < 10; i++)
{
    System.Threading.Thread.Sleep(1);
    Console.WriteLine(DateTime.Now.Ticks - now);
}

will give you an output of (for example):

0
0
0
0
0
0
0
156254
156254
156254

Similarly, if you look at DateTime.Now.Millisecond, you'll get values in rounded chunks of 15.625ms: 15, 31, 46, etc.

This particular behaviour varies from system to system, but there are other resolution-related gotchas in this date/time API.


Path.Combine

A great way to combine file paths, but it doesn't always behave the way you'd expect.

If the second parameter starts with a \ character, it won't give you a complete path:

This code:

string prefix1 = "C:\\MyFolder\\MySubFolder";
string prefix2 = "C:\\MyFolder\\MySubFolder\\";
string suffix1 = "log\\";
string suffix2 = "\\log\\";

Console.WriteLine(Path.Combine(prefix1, suffix1));
Console.WriteLine(Path.Combine(prefix1, suffix2));
Console.WriteLine(Path.Combine(prefix2, suffix1));
Console.WriteLine(Path.Combine(prefix2, suffix2));

Gives you this output:

C:\MyFolder\MySubFolder\log\
\log\
C:\MyFolder\MySubFolder\log\
\log\

When you start a process (using System.Diagnostics) that writes to the console, but you never read the Console.Out stream, after a certain amount of output your app will appear to hang.

Value objects in mutable collections

struct Point { ... }
List<Point> mypoints = ...;

mypoints[i].x = 10;

has no effect.

mypoints[i] returns a copy of a Point value object. C# happily lets you modify a field of the copy. Silently doing nothing.


Update: This appears to be fixed in C# 3.0:

Cannot modify the return value of 'System.Collections.Generic.List<Foo>.this[int]' because it is not a variable

For C/C++ programmers, the transition to C# is a natural one. However, the biggest gotcha I've run into personally (and have seen with others making the same transition) is not fully understanding the difference between classes and structs in C#.

In C++, classes and structs are identical; they only differ in the default visibility, where classes default to private visibility and structs default to public visibility. In C++, this class definition

    class A
    {
    public:
        int i;
    };

is functionally equivalent to this struct definition.

    struct A
    {
        int i;
    };

In C#, however, classes are reference types while structs are value types. This makes a BIG difference in (1) deciding when to use one over the other, (2) testing object equality, (3) performance (e.g., boxing/unboxing), etc.

There is all kinds of information on the web related to the differences between the two (e.g., here). I would highly encourage anyone making the transition to C# to at least have a working knowledge of the differences and their implications.

Garbage collection and Dispose(). Although you don't have to do anything to free up memory, you still have to free up resources via Dispose(). This is an immensely easy thing to forget when you are using WinForms, or tracking objects in any way.

foreach loops variables scope!

var l = new List<Func<string>>();
var strings = new[] { "Lorem" , "ipsum", "dolor", "sit", "amet" };
foreach (var s in strings)
{
    l.Add(() => s);
}

foreach (var a in l)
    Console.WriteLine(a());

prints five "amet", while the following example works fine

var l = new List<Func<string>>();
var strings = new[] { "Lorem" , "ipsum", "dolor", "sit", "amet" };
foreach (var s in strings)
{
    var t = s;
    l.Add(() => t);
}

foreach (var a in l)
    Console.WriteLine(a());

The contract on Stream.Read is something that I've seen trip up a lot of people:

// Read 8 bytes and turn them into a ulong
byte[] data = new byte[8];
stream.Read(data, 0, 8); // <-- WRONG!
ulong data = BitConverter.ToUInt64(data);

The reason this is wrong is that Stream.Read will read at most the specified number of bytes, but is entirely free to read just 1 byte, even if another 7 bytes are available before end of stream.

It doesn't help that this looks so similar to Stream.Write, which is guaranteed to have written all the bytes if it returns with no exception. It also doesn't help that the above code works almost all the time. And of course it doesn't help that there is no ready-made, convenient method for reading exactly N bytes correctly.

So, to plug the hole, and increase awareness of this, here is an example of a correct way to do this:

    /// <summary>
    /// Attempts to fill the buffer with the specified number of bytes from the
    /// stream. If there are fewer bytes left in the stream than requested then
    /// all available bytes will be read into the buffer.
    /// </summary>
    /// <param name="stream">Stream to read from.</param>
    /// <param name="buffer">Buffer to write the bytes to.</param>
    /// <param name="offset">Offset at which to write the first byte read from
    ///                      the stream.</param>
    /// <param name="length">Number of bytes to read from the stream.</param>
    /// <returns>Number of bytes read from the stream into buffer. This may be
    ///          less than requested, but only if the stream ended before the
    ///          required number of bytes were read.</returns>
    public static int FillBuffer(this Stream stream,
                                 byte[] buffer, int offset, int length)
    {
        int totalRead = 0;
        while (length > 0)
        {
            var read = stream.Read(buffer, offset, length);
            if (read == 0)
                return totalRead;
            offset += read;
            length -= read;
            totalRead += read;
        }
        return totalRead;
    }

    /// <summary>
    /// Attempts to read the specified number of bytes from the stream. If
    /// there are fewer bytes left before the end of the stream, a shorter
    /// (possibly empty) array is returned.
    /// </summary>
    /// <param name="stream">Stream to read from.</param>
    /// <param name="length">Number of bytes to read from the stream.</param>
    public static byte[] Read(this Stream stream, int length)
    {
        byte[] buf = new byte[length];
        int read = stream.FillBuffer(buf, 0, length);
        if (read < length)
            Array.Resize(ref buf, read);
        return buf;
    }

Today I fixed a bug that eluded for long time. The bug was in a generic class that was used in multi threaded scenario and a static int field was used to provide lock free synchronisation using Interlocked. The bug was caused because each instantiation of the generic class for a type has its own static. So each thread got its own static field and it wasn't used a lock as intended.

class SomeGeneric<T>
{
    public static int i = 0;
}

class Test
{
    public static void main(string[] args)
    {
        SomeGeneric<int>.i = 5;
        SomeGeneric<string>.i = 10;
        Console.WriteLine(SomeGeneric<int>.i);
        Console.WriteLine(SomeGeneric<string>.i);
        Console.WriteLine(SomeGeneric<int>.i);
    }
}

This prints 5 10 5

Enumerables can be evaluated more than once

It'll bite you when you have a lazily-enumerated enumerable and you iterate over it twice and get different results. (or you get the same results but it executes twice unnecessarily)

For example, while writing a certain test, I needed a few temp files to test the logic:

var files = Enumerable.Range(0, 5)
    .Select(i => Path.GetTempFileName());

foreach (var file in files)
    File.WriteAllText(file, "HELLO WORLD!");

/* ... many lines of codes later ... */

foreach (var file in files)
    File.Delete(file);

Imagine my surprise when File.Delete(file) throws FileNotFound!!

What's happening here is that the files enumerable got iterated twice (the results from the first iteration are simply not remembered) and on each new iteration you'd be re-calling Path.GetTempFilename() so you'll get a different set of temp filenames.

The solution is, of course, to eager-enumerate the value by using ToArray() or ToList():

var files = Enumerable.Range(0, 5)
    .Select(i => Path.GetTempFileName())
    .ToArray();

This is even scarier when you're doing something multi-threaded, like:

foreach (var file in files)
    content = content + File.ReadAllText(file);

and you find out content.Length is still 0 after all the writes!! You then begin to rigorously checks that you don't have a race condition when.... after one wasted hour... you figured out it's just that tiny little Enumerable gotcha thing you forgot....

MemoryStream.GetBuffer() vs MemoryStream.ToArray(). The former returns the whole buffer, the latter just the used portion. Yuck.

There is a whole book on .NET Gotchas

My favourite is the one where you create a class in C#, inherit it to VB and then attempt to re-inherit back to C# and it doesnt work. ARGGH

Dictionary<,>: "The order in which the items are returned is undefined". This is horrible, because it can bite you sometimes, but work others, and if you've just blindly assumed that Dictionary is going to play nice ("why shouldn't it? I thought, List does"), you really have to have your nose in it before you finally start to question your assumption.

(Similar question here.)

If you're coding for MOSS and you get a site reference this way:

SPSite oSiteCollection = SPContext.Current.Site;

and later in your code you say:

oSiteCollection.Dispose();

From MSDN:

If you create an SPSite object, you can use the Dispose method to close the object. However, if you have a reference to a shared resource, such as when the object is provided by the GetContextSite method or Site property (for example, SPContext.Current.Site), do not use the Dispose method to close the object, but instead allow Windows SharePoint Services or your portal application to manage the object. For more information about object disposal, see Best Practices: Using Disposable Windows SharePoint Services Objects.

This happens to every MOSS programmer and some point.

I frequently have to remind myself that DateTime is a value type, not a ref type. Just seems too weird to me, especially considering the variety of constructors for it.

My worst one so far I just figured out today... If you override object.Equals(object obj), you can wind up discovering that:

((MyObject)obj).Equals(this);

does not behave the same as:

((MyObject)obj) == this;

One will call your overriden function, the other will NOT.

The worst thing it happen to me was the webBrowser documentText issue:

http://geekswithblogs.net/paulwhitblog/archive/2005/12/12/62961.aspx#107062

the AllowNavigation solutions works in Windows forms...

but in compact framework the property doesn't exists...

...so far the only workaround I found was to rebuild the browser control:

http://social.msdn.microsoft.com/Forums/it-IT/netfxcompact/thread/5637037f-96fa-48e7-8ddb-6d4b1e9d7db9

But doing so, you need to handle the browser history at hands ... :P

Related object and foreign key out of sync

Microsoft have admitted to this bug.

I have a class Thing, which has a FK to Category. Category does not have a defined relationship to Thing, so as not to pollute the interface.

var thing = CreateThing(); // does stuff to create a thing
var category = GetCategoryByID(123); // loads the Category with ID 123
thing.Category = category;
Console.WriteLine("Category ID: {0}", thing.CategoryID); 

Output:

Category ID: 0

Similarly:

var thing = CreateThing();
thing.CategoryID = 123;
Console.WriteLine("Category name: {0}", order.Category.Name);

throws a NullReferenceException. Related object Category does not load the Category record with ID 123.

After you submit changes to the DB, though, these values do get synched. But before you visit the DB, the FK value and related object function practically independently!

(Interestingly, the failure to synch the FK value with the related object only seems to happen when there is no child relationship defined, i.e. Category has no "Things" property. But the "load on demand" when you just set the FK value NEVER works.)

GOTCHA!

mystring.Replace("x","y")

While it looks like it should do the replacement on the string it's being invoked on it actually returns a new string with the replacements made without changing the string it's invoked on. You need to remember that strings are immutable.

The following will not catch the exception in .Net. Instead it results in a StackOverflow exception.

private void button1_Click( object sender, EventArgs e ) {
    try {
        CallMe(234);
    } catch (Exception ex) {
        label1.Text = ex.Message.ToString();
    }
}
private void CallMe( Int32 x ) {
    CallMe(x);
}

For the commenters (and downvotes):
It would be extremely rare for a stack overflow to be this obvious. However, if one occurs you aren't going to catch the exception and will likely spend several hours trying to hunt down exactly where the problem is. It can be compounded if the SO occurs in little used logic paths, especially on a web app where you might not know the exact conditions that kicked off the issue.

This is the exact same situation as the accepted answer to this question (https://stackoverflow.com/a/241194/2424). The property getter on that answer is essentially doing the exact same thing as the above code and crashing with no stack trace.

LinqToSQL and the empty set aggregate

See this question.

If you have a LinqToSql query on which you are running an aggregate - if your resultset is empty, Linq can't work out what the data type is, even though it's been declared.

e.g. Suppose you have a table Claim with a field Amount, which in LinqToSql is of type decimal.

var sum = Claims.Where(c => c.ID < 0).Sum(c => c.Amount);

Obviously no claims have an ID less than zero, so you'd expect to see sum = null, right? Wrong! You get an InvalidOperationException, because the SQL query underlying the Linq query doesn't have a data type. You have to tell Linq explicitly that it's a decimal! Thus:

var sum = Claims.Where(c => c.ID < 0).Sum(c => (decimal?)c.Amount);

This is really dumb and IMO a design bug on Microsoft's part.

GOTCHA!

Linq2SQL: The mapping of interface member [...] is not supported.

If you do a Linq2Sql query on an object that implements an interface, you get a very odd behavior. Let's say you have a class MyClass that implements an interface IHasDescription, thus:

public interface IHasDescription {
  string Description { get; set; }
}

public partial class MyClass : IHasDescription { }

(The other half of MyClass is a Linq2Sql generated class, including the property Description.)

Now you write some code (usually this happens in a generic method):

public static T GetByDescription<T>(System.Data.Linq.Table<T> table, string desc) 
  where T : class, IHasDescription {
  return table.Where(t => t.Description == desc).FirstOrDefault();
}

Compiles fine - but you get a runtime error:

NotSupportedException: The mapping of interface member IHasDescription.Description is not supported.

Now whaddaya do about that? Well, it's obvious really: just change your == to .Equals(), thus:

return table.Where(t => t.Description.Equals(desc)).FirstOrDefault();

And everything works fine now!

See here.

Some code:

        List<int> a = new List<int>();
        for (int i = 0; i < 10; i++)
        {
            a.Add(i);
        }

        var q1 = (from aa in a
                  where aa == 2
                  select aa).Single();

        var q2 = (from aa in a
                  where aa == 2
                  select aa).First();

q1 - in this query check all integers in List; q2 - check integers until find "right" integer.

Related