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

61

384

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.

Jin answered 27/10, 2008 at 19:30 Comment(15)
return DateTime.Now.AddDays(1);Thyroiditis
AFAIK, the built in value types are all immutable, at least in that any method included with the type returns a new item rather than modifying the existing item. At least, I can't think of one off the top of my head that doesn't do this: all nice and consistent.Antepenult
community wiki, so much spam in SO now. When question are subjective (no final answer) it should be Community Wiki.Nephrotomy
Mutable value type: System.Collections.Generics.List.Enumerator :( (And yes, you can see it behaving oddly if you try hard enough.)Chungchungking
Lol, I knew there would be exceptions. Enumerator seems kind of... special anyway. Let me refine that to types directly under the System namespace, though even there you might still find something.Antepenult
The intellisense gives you all the info you need. It says it returns a DateTime object. If it just altered the one you passed in, it would be a void method.Karlow
Not necessarily: StringBuilder.Append(...) returns "this" for example. That's quite common in fluent interfaces.Chungchungking
That's why I like the '!' convention in Lisp, AddDays (returns new value) and AddDays! (modifies existing value) are easily and instantly distinguishable.Kalynkam
Don't you just hate all this what is your biggest/funniest/etc <whatever> posts on SO ?Meekins
The structs in System.Drawing (Point, Size, Rectangle, and float versions) are all mutable. Color isn't, though.Carrack
Should be named dt.NextDays(1);, programmers won't have a notion that it changes its own value. In the same vein that data structure constructs (e.g. node->next, node.next) doesn't modify the value of node. And.. it's too late now, they could give dt.AddDays(1) the semantics that it add days in its own value.Clannish
I agree that the given example is a naming issue. The method name implies that it adds to the date. Something like dt.PlusDays(1) would be clearer. Another issue with that method is it only accepts a double value, so the result isn't exact.Kill
I would win this question is a stupid person did not closed this topic. I know the most evil bug in the world. Please see codeproject.com/Feature/…Parthena
@bluefeet, as users have provided >= 62 answers (including Jon Skeet), many of them with hundreds of upvotes, apparently then the users want even these types of questions. Either this does constitute an appropriate type of question, or it's time for a constitutional amendment to all the restricting rules of SO.Scandura
5.add(2); 5 should still be 5.Giaimo
Z
308
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.)

Zenda answered 27/10, 2008 at 19:30 Comment(19)
and SO appropriate for this site :)Agriculturist
I put underscores on the private member, helps a lot!Oudh
This one takes the prize due to its evilocity and Stack Overflow-related irony.Jin
If i'm not mistaken, resharper can warn you of this possible problemPuck
I use automatic properties where I can, stops this kind of problem alot ;)Grajeda
I've had same problem when overriding a property and forgetting to put base.PropertyName instead of PropertyName.Murmur
I know it is not possible to programatically ascertain an endless loop, but this is such a basic one, I've always thought VS should issue a warning on this one!Chirurgeon
I've actually seen this kind of thing crash the VS ide when the property is exposed during design time.Barometry
This is a GREAT reason to use prefixes for your private fields (there are others, but this is a good one): _myVar, m_myVarBluepencil
@jrista: O please NO... not m_ ... aargh the horror...Dropper
@fretje: if you like crashes better than m_'s...Clack
This one is done really fast, but can eat up some time. Nasty!Pullen
just use _myVariableName for the private field.Sven
I think "my" prefix works great: private int myAge; public int Age { get { return myAge; } }Gleam
Just did this the other day, looking at my code I realized I was calling the property inside the property. And whoever said use m_ should be shot ;-)Guaranty
The upshot is that this is VERY unlikely to happen in released code, if you ever tested it at all. Unlike lots of other gotchas in this thread, this one is caught the first time this code executes.Rawlins
I don't like the 'm_', 's_', etc. prefixes, so I rather reference fields like this.fieldName instead of just fieldName. For static fields, I use an underscore prefix: _staticFieldName, for the lack of a better idea.Fraya
Resharper says: "Function is recursive on all paths." , underlines it blue, and puts a circle icon with an arrow in the breakpoint bar. That would throw up enough red flags for me! (shameless plug for resharper ... and you can avoid all those ugly m_ variables)Calvo
There are many reasons you should never name two members the same word with only case differences. One of which is the fact that VB.NET is case-insensitive, and any C# interface exposed to VB.NET will only present the first of the similarly-named variables for consumption.Quartz
C
258

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.

Chungchungking answered 27/10, 2008 at 19:47 Comment(19)
The iterator example is devious!Pyroxylin
However, it IS how it's supposed to work. It's tricky to get your head around it, but with some playing around with it it's actually pretty useful.Workable
why not split this into 3 answer so we can vote each one up instead of all together?Oudh
@chakrit: In retrospect, that would probably have been a good idea, but I think it's too late now. It might also have looked like I was just trying to get more rep...Chungchungking
Actually Type.GetType works if you provide the AssemblyQualifiedName. Type.GetType("System.ServiceModel.EndpointNotFoundException, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");Bordie
Hey, wait!!! I can't get the # 1) of your brainteasers... "Derived.Foo(object)" can you explain why int is resolved as an object? O_O;;Radley
@kentaromiura: The overload resolution starts at the most derived type and works up the tree - but only looking at methods originally declared in the type it's looking at. Foo(int) overrides the base method, so isn't considered. Foo(object) is applicable, so overload resolution stops there. Odd, I know.Chungchungking
Relevant to your "Anonymous Methods" gotcha, I just came across this the other day: blogs.msdn.com/ericlippert/archive/2009/11/12/…Ruiz
can any body pls explain this line 'ThreadStart ts = delegate { Console.WriteLine(i); };'... m not getting what it is doin' exactlyBeckett
@FosterZ: It's creating a delegate of type ThreadStart which prints the current value of i to the console.Chungchungking
@JonSkeet, you mentioned that due to scheduling, your example may not print out 1, 2, 3, 4, 5, 6, 7, 8, 9 etc. Does the same principle apply to the Parallel.For / Parallel.ForEach loops? (I have just asked this question here: #13142599)Genarogendarme
@activwerx: Yes - when you're doing things in parallel, you shouldn't expect them to execute in the original order.Chungchungking
I think that the last example is performing right as it should. It may look strange, though - as can look strange any continuation-like control structure.Millisent
@SargeBorsch: It depends on what you mean by "as it should". It follows the specs, certainly - but it doesn't behave as many people expect it to.Chungchungking
With regards to the variable vs value capture issue, notice that C# 5.0 has changed behaviour for foreach loops (but not for loops) specifically to remove the gotcha aspect of it.Morello
I dont get the third example.Giaimo
@BluE: I'm afraid without any more information, I can't really help you. Do you mean you don't understand the "setup" (i.e. the way in which it's surprising) or what it is that leads to that surprise?Chungchungking
I guess both?! I don't understand the control flow there. You are talking about the iterator IEnumerable<char>, but why do we need to wait for it before it's even used? So why would CapitalLetters not get called in the example?Giaimo
@BluE: CapitalLetters is called, but none of the code within the body of the method will execute - because that's how iterators work... (I don't think I mentioned "waiting" for anything though... it's a matter of starting to iterate over the returned iterator.)Chungchungking
B
202

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

Borough answered 27/10, 2008 at 19:30 Comment(7)
brilliant find! you're not a programmer, you're a real debugger.Steadfast
This was driving me crazy last week, but for a static constructor as opposed to a lazy loader. No matter how hard I tried, I could not get it to hit my breakpoints, even though the code was clearly being run. Ended up having to use the low-tech Debug.WriteLine approach.Ultan
I've run into this even hovering over the variable, not just the watch window.Pekoe
Decorate your property with [DebuggerBrowsable(DebuggerBrowsableState.Never)] or [DebuggerDisplay("<loaded on demand>")].Decollate
If you are developing a framework class and want watch window functionality without altering the runtime behavior of a lazily-constructed property, you can use a debugger type proxy to return the value if it's already been constructed, and a message that the property hasn't been constructed if that's the case. The Lazy<T> class (in particular for its Value property) is one example of where this is used.Shirley
I recall someone who (for some reason I can't fathom) changed the value of the object in an overload of ToString. Every time he hovered over it the tooltip gave him a different value - he couldn't figure it out...Supposititious
I had faced this situation and consider it as a natural (if unexpected for some) consequence to having watch/locals/hover functionality. BTW, the naming is just hilarious!Wainwright
W
195

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); 
}
Wellgroomed answered 27/10, 2008 at 19:30 Comment(6)
Very luckily, I got taught about this in my first week by someone and find it in more senior developers' code. Is: catch() { throw; } The same as the second code snippet? catch(Exception e) { throw; } only it doesn't create an Exception object and populate it?Proctor
Besides the error of using throw ex (or throw e) instead of just throw, I have to wonder what cases there are when it's worth catching an exception only to throw it again.Lipcombe
@Kyralessa: there are many cases: for instance, if you want to rollback a transaction, before the caller gets the exception. You rollback and then rethrow.Exponential
I see this all the time where people catch and rethrow exceptions just because they are taught that they must catch all exceptions, not realising that it will be caught further up the call stack. It drives me nuts.Sven
@Kyralessa the biggest case is when you have to do logging. Log the error in catch, and rethrow..Fissile
The rethrowing-exceptions one isn't exclusive to .Net, it applies to Java as well. Very true that it's correct to omit the variable and simply throw.Solicitude
A
146

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).

Aristippus answered 27/10, 2008 at 19:49 Comment(9)
I did not know that. Good thing I usually use Environment.TickCountJin
About a year or so ago, this one burned me for weeksSaucedo
Yeah, that one has got me too. I think it should be TimeSpan.SecondsPart or something to make it more clear what it represents.Zoara
On re-reading this, I have to wonder why TimeSpan even has a Seconds property at all. Who gives a rat's ass what the seconds portion of a timespan is, anyway? It's an arbitrary, unit-dependent value; I can't conceive of any practical use for it.Jin
Makes sense to me that TimeSpan.TotalSeconds would return... the total number of seconds in the time span.Hitherward
@Jin the property is useful. What if I want to display timespan broken out in pieces? E.g. let's say your Timespan represents duration of '3 hours 15 minutes 10 seconds'. How can you access this information without Seconds, Hours, Minutes properties?Yaws
@Yaws You should easily modulus the TotalSeconds by 60 to find the 10, but I agree that having a Seconds property is more friendly. Either way, you should never blindly assume what a property does based on its name without testing the result.Quartz
In similar APIs, I've used SecondsPart and SecondsTotal to distinguish the two.Chancy
I created an issue to ask for renaming these (of course properly, which is deprecating and adding new ones) github.com/dotnet/runtime/issues/58848 . Please vote it if it has bitten you.Wainwright
B
81

Leaking memory because you didn't un-hook events.

This even caught out some senior developers I know.

Imagine a WPF form with lots of things in it, and somewhere in there you subscribe to an event. If you don't unsubscribe then the entire form is kept around in memory after being closed and de-referenced.

I believe the issue I saw was creating a DispatchTimer in the WPF form and subscribing to the Tick event, if you don't do a -= on the timer your form leaks memory!

In this example your teardown code should have

timer.Tick -= TimerTickEventHandler;

This one is especially tricky since you created the instance of the DispatchTimer inside the WPF form, so you would think that it would be an internal reference handled by the Garbage Collection process... unfortunately the DispatchTimer uses a static internal list of subscriptions and services requests on the UI thread, so the reference is 'owned' by the static class.

Beatnik answered 27/10, 2008 at 19:30 Comment(6)
The trick is to always release all event subscriptions you create. If you begin to rely on Forms doing it for you, you can be sure you'll get into the habit and one day will forget to release an event somewhere where it needs to be done.Aesthetic
There is an MS-connect suggestion for weak reference events here which would solve this problem, though in my opinion we should just entirely replace the incredibly poor event model with a weakly-coupled one, like that used by CAB.Chancy
+1 from me, thanks! Well, no thanks for the code review work I had to do!Sheliasheline
@BlueRaja-DannyPflughoeft With weak events you have another gotcha - you cannot subscribe lambdas. You cannot write timer.Tick += (s, e,) => { Console.WriteLine(s); }Scalise
@Scalise yes lambdas make it even harder, you would have to save your lambda to a variable and use that in your teardown code. Kinda destroys the simplicity of writing lambdas doesn't it?Beatnik
This is only a problem if the publisher is lower in the reference tree (i.e., closer to the application root) like a static class or parent class. It is equivalent to the publisher holding a reference to the subscriber. If you remove all other references to the subscriber, the publisher will still have a reference thereby keeping it from being collected and facilitating the memory problem. People like to get a bit overzealous with this. I mean it doesn't hurt anything, but in many cases it is unnecessary and increases complexity.Hurricane
G
63

Maybe not really a gotcha because the behavior is written clearly in MSDN, but has broken my neck once because I found it rather counter-intuitive:

Image image = System.Drawing.Image.FromFile("nice.pic");

This guy leaves the "nice.pic" file locked until the image is disposed. At the time I faced it I though it would be nice to load icons on the fly and didn't realize (at first) that I ended up with dozens of open and locked files! Image keeps track of where it had loaded the file from...

How to solve this? I thought a one liner would do the job. I expected an extra parameter for FromFile(), but had none, so I wrote this...

using (Stream fs = new FileStream("nice.pic", FileMode.Open, FileAccess.Read))
{
    image = System.Drawing.Image.FromStream(fs);
}
Galbraith answered 27/10, 2008 at 19:30 Comment(4)
I agree that this behavior makes no sense. I can't find any explanation for it other than "this behavior is by design".Jin
Oh and what's great about this workaround is if you try to call Image.ToStream (I forget the exact name off hand) later it won't work.Tarazi
need to check some code. Brb.Zygo
@EsbenSkovPedersen Such a simple but funny & dry comment. Made my day.Keyway
W
51

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).

Workable answered 27/10, 2008 at 19:30 Comment(5)
That's why I moved to MVC... viewstate headaches...Oudh
There was a whole other question devoted specifically to ASP.NET gotchas (deservedly so). The basic concept of ASP.NET (making web apps seem like windows apps for the developer) is so horribly misguided that I'm not sure it even counts as a "gotcha".Jin
MusiGenesis I wish I could up vote your comment a hundred times.Schear
@Jin It seems misguided now, but at the time, people wanted their web applications (applications being the key word - ASP.NET WebForms wasn't really designed to host a blog) to behave the same as their windows applications. This only changed relatively recently and a lot of people still "aren't quite there". The whole problem was that the abstraction was way too leaky - web didn't behave like a desktop application so much it lead to confusion in almost everyone.Nelan
Ironically enough, the first thing I ever saw about ASP.NET was a video from Microsoft demonstrating how easily you could create a blog site using ASP.NET!Jin
P
51

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.

Pyroxylin answered 27/10, 2008 at 19:47 Comment(8)
You've got extra spaces in there which make it all wrong - but if you take the spaces out, the last line will still be true as "my" + "string" is still a constant.Chungchungking
ack! you're right :) ok, I edited a bit.Pyroxylin
a warning is generated on such uses.Oudh
Yes, one of the biggest flaws with the C# language is the == operator in class Object. They should have forced us to use ReferenceEquals.Jaunitajaunt
Anyone that has programmed in C would think of things this way straight off the bat. Course, you'd think that it should be ==(string) isn't it virtual?Ascent
@Earlz: operators in C# are never virtual.Pyroxylin
@Jimmy, ah, where thats where the problem is then.Ascent
Thankfully, since 2.0 we have had generics. There is less to worry about if you're using List<string> in the example above instead of ArrayList. Plus we've gained performance from it, yay! I'm always rooting out old references to ArrayLists in our legacy code.Newmann
P
50
[Serializable]
class Hello
{
    readonly object accountsLock = new object();
}

//Do stuff to deserialize Hello with BinaryFormatter
//and now... accountsLock == null ;)

Moral of the story : Field initialisers are not run when deserializing an object

Parthena answered 27/10, 2008 at 19:30 Comment(1)
Yes, I hate .NET serialization for not running the default constructor. I wish it were impossible to construct an object without calling any constructors, but alas it isn't.Rawlins
P
46

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!

Psychognosis answered 27/10, 2008 at 19:30 Comment(5)
Changed mm to MM - mm is minutes, and MM is months. Another gotcha, I guess...Filial
I could see how this would be a gotcha if you didn't know it (I didn't)...but I'm trying to figure out when you would want the behavior where you're specifically trying to print a date that doesn't match what your regional settings are.Transcalent
@Beska: Because you are writing to a file, that needs to be in a specific format, with a specified date format.Creswell
I am of the opinion that the defaults being localized is worse than the other way around. At least of the developer ignored localization completely the code works on machines localized differently. This way, the code probably doesn't work.Tarazi
Actually I believe the correct way to do this would be DateTime.ToString("dd/MM/yyyy", CultureInfo.InvariantCulture);Chancy
S
45

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

Shriner answered 27/10, 2008 at 19:32 Comment(19)
I hope that wouldn't actually bite people though - I really hope they wouldn't write it in the first place! (It's interesting anyway, of course.)Chungchungking
pre-increment actually works, it's post-increment that doesn't. I changed this.Fearless
tvanfosson thanks for the fix there, I noticed it right after posting. I agree that you SHOULDN'T see it, but man I've seen much worse stuff actually.....Shriner
I agree with Chris, this isn't very obscure. It's very handy to be able to have the increment after the assignment.Gait
At least, in C#, the results are defined, if unexpected. In C++, it could be 0 or 1, or any other result including program termination!Slightly
This isn't a gotcha; x=x++ -> x = x, then increment x....x=++x -> increment x then x = xMilitary
I'm teaching C# next year and that is exactly the sort of thing a student might do! And if you are not expecting it can be a trial to detect.Roadability
If i understand it Correctly, it is functionally equivalent to int temp = x; x++; x = temp;Volsung
This Works: int x = 0; x++; return x;Lazare
@Kevin: I don't think it's quite that simple. If x=x++ were equivalent to x=x followed by x++, then the result would be x = 1. Instead, I think what happens is first the expression to the right of the equals sign is evaluated (giving 0), then x is incremented (giving x = 1), and finally the assignment is performed (giving x = 0 once again).Anselmo
@Tim Goodman I agree, what I said made no sense.Military
It is not so obvious at all. In C/C++, this is actually undefined code - x could be 0 or 1, depending on the compiler!Chancy
x=++x; return x; //is what you're looking forSchear
-1: this is exactly as expected, due to the very well defined execution order of C#.Rawlins
"At least, in C#, the results are defined" They aren't because this is not thread safe.Trelliswork
Just a simple miss understanding between x++ and ++x. I believe you are looking for: int x = 0; x = ++x; return x;Conscription
@Conscription - No it's not. There is never a reason for the x= to be part of a statement with ++x or 'x++'. It a misunderstanding of exactly what the increment operator does.Slightly
Actually came across this bug in some inherited code. It's so odd looking, it caught my eye. Don't know why they ever thought it worked. I used it in an intro interview.Raimundo
I usually write x += 1 in statements. I usually write pre-increment in loops. I almost never use post-increment. It’s confusing.Nonstandard
T
41

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\
Trueblood answered 27/10, 2008 at 19:30 Comment(6)
The quantization of times in ~15ms intervals isn't because of a lack of accuracy in the underlying timing mechanism (I neglected to elaborate on this earlier). It's because your app is running inside a multi-tasking OS. Windows checks in with your app every 15ms or so, and during the little time slice it gets, your app processes all of the messages that were queued up since your last slice. All of your calls within that slice return the exact same time because they're all made at effectively the exact same time.Jin
@MusiGenesis: I know (now) how it works, but it seems misleading to me to have such a precise measure which isn't really that precise. It's like saying that I know my height in nanometres when really I'm just rounding it to the nearest ten million.Trueblood
DateTime is quite capable of storing up to a single tick; it's DateTime.Now that isn't using that accuracy.Closefitting
The extra '\' is a gotcha to many unix/mac/linux folks. In Windows, if there's a leading '\', it's mean that we want to go the drive's root (i.e. C:) try it in a CD command to see what I mean.... 1) Goto C:\Windows\System32 2) Type CD \Users 3) Woah! Now you're at C:\Users ... GOT IT? ... Path.Combine(@"C:\Windows\System32", @"\Users") should returns \Users which means precisely the [current_drive_here]:\UsersOudh
Even without the 'sleep' this performs the same way. This has nothing to do with the app being scheduled every 15 ms. The native function called by DateTime.UtcNow, GetSystemTimeAsFileTime, appears to have a poor resolution.Infidel
@Infidel To be fair, it's a function to give you the current clock time. I wouldn't be the least surprised if it was only accurate to a second instead.Nelan
G
39

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.

Gyn answered 27/10, 2008 at 19:30 Comment(1)
The same can still happen when you redirect both stdout and stderr and use two ReadToEnd calls in sequence. For safe handling of both stdout and stderr you have to create a read thread for each of them.Outfoot
B
36

No operator shortcuts in Linq-To-Sql

See here.

In short, inside the conditional clause of a Linq-To-Sql query, you cannot use conditional shortcuts like || and && to avoid null reference exceptions; Linq-To-Sql evaluates both sides of the OR or AND operator even if the first condition obviates the need to evaluate the second condition!

Borough answered 27/10, 2008 at 19:30 Comment(1)
TIL. BRB, re-optimizing a few hundred LINQ queries...Hemicellulose
C
31

Using default parameters with virtual methods

abstract class Base
{
    public virtual void foo(string s = "base") { Console.WriteLine("base " + s); }
}

class Derived : Base
{
    public override void foo(string s = "derived") { Console.WriteLine("derived " + s); }
}

...

Base b = new Derived();
b.foo();

Output:
derived base

Chancy answered 27/10, 2008 at 19:30 Comment(11)
Damn, dude, you really have it in for C#, don't you?Jin
@Musi: No, it's just the language I work with the most :)Chancy
Weird, I thought this is completely obvious. If the declared type is Base, where should the compiler get the default value from if not Base? I’d have thought it’s a bit more gotcha that the default value can be different if the declared type is the derived type, even though the method called (statically) is the base method.Quintanilla
Just for the record. In C++ methods can have default arguments and there you have the exact same situation. Static default arguments. Good practice there is to not change default arguments on inherited methods.Toilsome
why would one implementation of a method get the default value of another implementation?Nearly
@Nearly Default arguments are resolved at compile-time, not runtime.Yip
I'd say this gotcha is the default parameters in general - people often don't realize that they're resolved at compile-time, rather than run-time.Nelan
@FredOverflow, my question was conceptual. Although the behavior makes sense wrt the implementation, it's unintuitive and a likely source of errors. IMHO the C# compiler shouldn't allow changing default parameter values when overriding.Nearly
Can the subclass' default ever be accessed? Does it matter if the parent class is abstract or not?Kennithkennon
Interestingly enough, if both classes were to adhere to an interface with a default param set, it would be as-expected via the interface.Modiste
If you declare the base optional parameter value as null, it works as one would expect.Boult
H
28

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
Heyman answered 27/10, 2008 at 19:30 Comment(3)
I can see why that is confusing, considering that it does indeed work with arrays (contrary to your answer), but not with other dynamic collections, like List<Point>.Diego
You're right. Thanks. I fixed my answer :). arr[i].attr= is special syntax for arrays that you cannot code in library containers ;(. Why is (<value expression>).attr = <expr> allowed at all? Can it ever make sense?Heyman
@Bjarke Ebert: There are some cases where it would make sense, but unfortunately there is no way for the compiler to identify and permit those. Sample usage scenario: an immutable Struct which holds a reference to a square two-dimensional array along with a "rotate/flip" indicator. The struct itself would be immutable, so writing to an element of a read-only instance should be fine, but the compiler won't know that the property setter isn't actually going to write the struct, and thus won't allow it.Boden
C
26

Perhaps not the worst, but some parts of the .net framework use degrees while others use radians (and the documentation that appears with Intellisense never tells you which, you have to visit MSDN to find out)

All of this could have been avoided by having an Angle class instead...

Chancy answered 27/10, 2008 at 19:30 Comment(1)
I'm surprised this got so many upvotes, considering my other gotchas are significantly worse than thisChancy
T
22

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.

Teary answered 27/10, 2008 at 19:30 Comment(2)
So, the worst gotcha is people not bothering to take the time to learn the language before they use it?Chancy
@BlueRaja-DannyPflughoeft More like the classic gotcha of apparently similar languages - they use very similar keywords and in many cases syntax, but work in a lot different way.Nelan
S
19

Arrays implement IList

But don't implement it. When you call Add, it tells you that it doesn't work. So why does a class implement an interface when it can't support it?

Compiles, but doesn't work:

IList<int> myList = new int[] { 1, 2, 4 };
myList.Add(5);

We have this issue a lot, because the serializer (WCF) turns all the ILists into arrays and we get runtime errors.

Skald answered 27/10, 2008 at 19:30 Comment(11)
IMHO, the problem is that Microsoft doesn't have enough interfaces defined for collections. IMHO, it should have iEnumerable, iMultipassEnumerable (supports Reset, and guarantees multiple passes will match), iLiveEnumerable (would have partially-defined semantics if the collection changes during enumeration--changes may or may not appear in enumeration, but shouldn't cause bogus results or exceptions), iReadIndexable, iReadWriteIndexable, etc. Because interfaces can "inherit" other interfaces, this wouldn't have added much extra work, if any (it would save NotImplemented stubs).Boden
@supercat, that would be confusing as hell for beginners and certain long-time coders. I think the .NET collections and their interfaces are wonderfully elegant. But I appreciate your humility. ;)Hurricane
@Jordan: Since writing the above, I've decided that a better approach would have been to have both IEnumerable<T> and IEnumerator<T> support a Features property as well as some "optional" methods whose usefulness would be determined by what "Features" reported. I stand by my main point, though, which is that there are cases where code receiving an IEnumerable<T> will need stronger promises than IEnumerable<T> provides. Calling ToList would yield an IEnumerable<T> that upholds such promises, but would in many cases be needlessly expensive. I would posit that there should be...Boden
...a means by which code receiving an IEnumerable<T> could make a copy of the contents if needed but could refrain from doing so needlessly.Boden
Your option is absolutely not readable. When I see an IList in code I know what I am working with rather than having to probe a Features property. Programmers like to forget that an important feature of code is that it can be read by people not just computers. The .NET collections namespace is not ideal but it is good, and sometimes finding the best solution is not a matter of fitting a principle more ideally. Some of the worst code I have every worked with was code that tried to fit DRY ideally. I scrapped it and rewrote it. It was just bad code. I would not want to use your framework at all.Hurricane
You are also assuming that they can scrap a namespace that goes all the way back to the creation of .NET and start over. That's just not going to happen. I believe that this quirky usage is an artifact of some requirement that would have otherwise required a rewrite.Hurricane
@Jordan: Suppose an object method receives a reference to an IEnumerable<int> and wants to keep a reference to an IEnumerable<int> which will forevermore contain the same items as the received one does now. If the received reference identifies an immutable sequence, calling ToList() on it may be unnecessary and expensive (e.g. if the same enumerable list gets given to 100 objects, calling ToList needlessly may increase memory requirements a hundredfold). There is presently no means, however, by which code can determine whether it needs to make a copy of the list.Boden
@Jordan: Given things as they are, the best approach would IMHO be to add a means by which an interface could specify that methods which are missing in an implementation should be auto-generated to chain to static methods in a designated static class, and allow methods to be added to existing interfaces if suitable static methods are defined. Then calling Features on existing IEnumerable<T> instances would receive a response indicating, among other things, "I can't say if I'm immutable or not", but classes which are in fact immutable could be updated to report that.Boden
@supercat: If a method intends to keep a copy of the elements it is given, it should ask for something a little bit more concrete than IEnumerable(T) which is called a sequence for a reason. IEnumerable(T) is only something that can be enumerated. That is all it is. Forcing it to do more than that is just bad design. It just sounds like you are looking for optimization in the wrong area. Over optimization and wrong optimization are just as much a problem as under optimization. I have never had a perf problem caused by System.Collections.Hurricane
@Jordan: What type would you suggest? The method should be able to accept any implementation of IEnumerable<T> which is either immutable or else small enough to make ToList() reasonable. Having either an added interface method or extension method AsImmutable which would return an immutable copy of the list unless it reported itself as immutable or provided a means of requesting an immutable copy would mean the called method could simply store receivedSequence.AsImmutable() without having to worry about what it received. What better approach could you offer?Boden
Let us continue this discussion in chat.Hurricane
W
19

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.

Wife answered 27/10, 2008 at 19:30 Comment(7)
The using() block neatly solves this problem. Whenever you see a call to Dispose, you can immediately and safely refactor to use using().Levite
I think the concern was implementing IDisposable correctly.Persse
This is a big gotcha in .NET compact framework too, where resources is severely limited.Oudh
On the other hand, the using() habit can bite you unexpectedly, like when working with PInvoke. You don't want to dispose something that the API is still referencing.Jin
Implementing IDisposable correctly is very hard to and understand even the best advice I have found on this (.NET Framework Guidelines) can be confusing to apply until you finally "get it".Jorry
The best advice I ever found on IDisposable comes from Stephen Cleary, including three easy rules and an in-depth article on IDisposableRawlins
@JeremyFrey Not true, you might be disposing long after the initial block that declared the value has finished.Fang
R
18

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;
    }
Rawlins answered 27/10, 2008 at 19:30 Comment(1)
Or, in your explicit example: var r = new BinaryReader(stream); ulong data = r.ReadUInt64();. BinaryReader has a FillBuffer method too...Alcyone
B
18

The Nasty Linq Caching Gotcha

See my question that led to this discovery, and the blogger who discovered the problem.

In short, the DataContext keeps a cache of all Linq-to-Sql objects that you have ever loaded. If anyone else makes any changes to a record that you have previously loaded, you will not be able to get the latest data, even if you explicitly reload the record!

This is because of a property called ObjectTrackingEnabled on the DataContext, which by default is true. If you set that property to false, the record will be loaded anew every time... BUT... you can't persist any changes to that record with SubmitChanges().

GOTCHA!

Borough answered 27/10, 2008 at 19:30 Comment(2)
Iv just spent a day and a half (and loads of hair!) chasing down this BUG...Tangier
This is called a concurrency conflict and it is still a gotcha today even though there are certain ways around this now though they tend to be a bit heavy handed. DataContext was a nightmare. O_oHurricane
Z
18

MS SQL Server can't handle dates before 1753. Significantly, that is out of synch with the .NET DateTime.MinDate constant, which is 1/1/1. So if you try to save a mindate, a malformed date (as recently happened to me in a data import) or simply the birth date of William the Conqueror, you're gonna be in trouble. There is no built-in workaround for this; if you're likely to need to work with dates before 1753, you need to write your own workaround.

Zloty answered 27/10, 2008 at 19:30 Comment(5)
Quite frankly I think MS SQL Server has this right and .Net is wrong. If you do the research then you know that dates prior to 1751 get funky due to calendar changes, days completely skipped, etc. Most RDBMs have some cut off point. This should give you a starting point: ancestry.com/learn/library/article.aspx?article=3358Concenter
Also, the date is 1753.. Which was pretty much the first time that we have a continuous calendar without dates being skipped. SQL 2008 introduced the Date and datetime2 datetype which can accept dates from 1/1/01 to 12/31/9999. However, date comparisons using those types should be viewed with suspicion if you are really comparing pre-1753 dates.Concenter
Does it really make sense to do date comparisons with such dates? I mean, for History Channel this makes a lot of sense, but I don't see myself wanting to know the precise day of the week America was discovered.Fang
Through Wikipedia on Julian Day you can find a 13 line basic program CALJD.BAS published in 1984 that can do date calculations back to about 5000 BC, taking into account leap days and the skipped days in 1753. So I do not see why "modern" systems like SQL2008 should do worse. You might not be interested in a correct date representation in the 15th century, but others might, and our software should handle this without bugs. Another issue is leap seconds . . .Crepuscule
Because of these issues, systems often use the en.wikipedia.org/wiki/Proleptic_Gregorian_calendar, which means, a date in the past as if we'd be under the same calender rules we have today, even though it's historically incorrect. Or use better datetiming frameworks, such as @jon-skeet Jon Skeet's NodaTime nodatime.org.Scandura
S
18

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());
Squaw answered 27/10, 2008 at 19:30 Comment(6)
This is essentially equivalent to Jon's example with anonymous methods.Cimino
Save that it is even more confusing with foreach where the "s" variable is easier to mix with scoped variable. With common for-loops the index variable clearly is the same one for each iteration.Secunderabad
blogs.msdn.com/ericlippert/archive/2009/11/12/… and yes, wish the variable was scoped "properly".Rawlins
This was fixed in C# 5.Calebcaledonia
You are essentially just printing the same variable over and over again without changing it.Hurricane
@Calebcaledonia Not fixed, changed.Affectionate
S
16

Events

I never understood why events are a language feature. They are complicated to use: you need to check for null before calling, you need to unregister (yourself), you can't find out who is registered (eg: did I register?). Why isn't an event just a class in the library? Basically a specialized List<delegate>?

Skald answered 27/10, 2008 at 19:30 Comment(6)
Also, multithreading is painful. All these issues but the null-thing are fixed in CAB (whose features should really just be built into the language) - events are declared globally, and any method can declare itself to be a "subscriber" of any event. My only issue with CAB is that the global event names are strings rather than enums (which could be fixed by more intelligent enums, like Java has, which inherently work as strings!). CAB is difficult to set up, but there is a simple open-source clone available here.Chancy
I dislike the implementation of .net events. Event subscription should be handled by calling a method that adds the subscription and returns an IDisposable which, when Dispose'd, will delete the subscription. There's no need for a special construct combining an "add" and "remove" method whose semantics can be somewhat dodgy, especially if one attempts to add and later remove a multicast delegate (e.g. Add "B" followed by "AB", then remove "B" (leaving "BA") and "AB" (still leaving "BA"). Oops.Boden
@Boden How would you rewrite button.Click += (s, e) => { Console.WriteLine(s); }?Scalise
If I would have to be able to unsubscribe separately from other events, IEventSubscription clickSubscription = button.SubscribeClick((s,e)=>{Console.WriteLine(s);}); and unsubscribe via clickSubscription.Dispose();. If my object would keep all subscriptions throughout its lifetime, MySubscriptions.Add(button.SubscribeClick((s,e)=>{Console.WriteLine(s);})); and then MySubscriptions.Dispose() to kill all subscriptions.Boden
@Ark-kun: Having to keep objects that encapsulate outside subscriptions might seem like a nuisance, but regarding subscriptions as entities would make it possible to aggregate them with a type that can ensure they all get cleaned up, something which is otherwise very difficult.Boden
@Ark-kun: BTW, another thing about having subscriptions as entities is that if the event publisher keeps a linked list of the entities associated with its subscriptions, unsubscribing an object merely requires invalidating its subscription object--since the event publisher will have to poll attached entities anyway when an event fires, it can safely detach them at that time; if the event doesn't fire, the publisher can periodically check for invalidated subscriptions when adding new ones.Boden
A
16

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

Alum answered 27/10, 2008 at 19:30 Comment(3)
you can have a non-generic base class, that defines the statics, and inherit the generics from it. Although I never fell for this behavior in C# - I still remember the long debugging hours of some C++ templates... Eww! :)Svend
Weird, I thought this was obvious. Just think about what it should do if i had the type T.Quintanilla
The type parameter is part of the Type. SomeGeneric<int> is a different Type from SomeGeneric<string>; so of course each has its own public static int iMordecai
F
15

Just found a weird one that had me stuck in debug for a while:

You can increment null for a nullable int without throwing an excecption and the value stays null.

int? i = null;
i++; // I would have expected an exception but runs fine and stays as null
Fant answered 27/10, 2008 at 19:30 Comment(1)
That's the result of how C# leverages operations for nullable types. It is a bit similar to NaN consuming all you throw to it.Affectionate
O
14

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....

Oudh answered 27/10, 2008 at 19:30 Comment(4)
This is by design. It's called deferred execution. Among other things, it's meant to simulate TSQL constructs. Every time you select from a sql view you get different results. It also allows chaining which is helpful for remote data stores, such as SQL Server. Otherwise x.Select.Where.OrderBy would send 3 separate commands to the database...Scandura
@AYS did you miss the word "Gotcha" in the question title?Oudh
I thought gotcha meant an oversight of the designers, not something intentional.Scandura
Maybe there should be another type for non-restartable IEnumerables. Like, AutoBufferedEnumerable? One could implement it easily. This gotcha seem mostly due to the programmer's lack of knowledge, I don't think there's anything wrong with the current behavior.Cardiganshire
Z
11

This is a super-gotcha that I wasted 2 days troubleshooting. It didn't throw any exceptions it just crashed the web-server with some weird error messages. I could not reproduce the problem in DEV. Moreover the experiments with the project build settings somehow made it go away in the PROD, then it came back. Finally I got it.

Tell me if you see a problem in the following piece of code:

private void DumpError(Exception exception, Stack<String> context)
{
    if (context.Any())
    {
        Trace.WriteLine(context.Pop());
        Trace.Indent();
        this.DumpError(exception, context);
        Trace.Unindent();
    }
    else
    {
        Trace.WriteLine(exception.Message);
    }
}

So if you value your sanity:

!!! Never ever ever put any logic to Trace methods !!!

The code must have looked like this:

private void DumpError(Exception exception, Stack<String> context)
{
    if (context.Any())
    {
        var popped = context.Pop();
        Trace.WriteLine(popped);
        Trace.Indent();
        this.DumpError(exception, context);
        Trace.Unindent();
    }
    else
    {
        Trace.WriteLine(exception.Message);
    }
}
Zigzag answered 27/10, 2008 at 19:30 Comment(1)
This would have gotten me. +1. I never knew .NET would throw away code (like assert does in C++ debug mode)Eden
C
10
TextInfo textInfo = Thread.CurrentThread.CurrentCulture.TextInfo;

textInfo.ToTitleCase("hello world!"); //Returns "Hello World!"
textInfo.ToTitleCase("hElLo WoRld!"); //Returns "Hello World!"
textInfo.ToTitleCase("Hello World!"); //Returns "Hello World!"
textInfo.ToTitleCase("HELLO WORLD!"); //Returns "HELLO WORLD!"

Yes, this behavior is documented, but that certainly doesn't make it right.

Chancy answered 27/10, 2008 at 19:30 Comment(2)
I disagree - when a word is in all caps, it can have special meaning that you don't want to mess up with Title Case, e.g. "president of the USA" -> "President Of The USA", not "President Of The Usa".Borough
@Shaul: In which case, they should specify this as a parameter to avoid confusion, because I've never met anyone who expected this behaviour ahead of time - which makes this a gotcha!Chancy
A
9

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

Aristippus answered 27/10, 2008 at 19:30 Comment(2)
Yeah, streams can bite you if you're unaware. I used to have a really bad habit of not remembering to close them.Jin
@MusiGenesis, use using(Stream stream = new ...) { } .Rata
A
8

Static constructors are executed under lock. As a result, calling threading code from static constructor might result in deadlock. Here is an example that demonstrates it:

using System.Threading;
class Blah
{
    static void Main() { /* Won’t run because the static constructor deadlocks. */ }

    static Blah()
    {
        Thread thread = new Thread(ThreadBody);
        thread.Start();
        thread.Join();
    }

    static void ThreadBody() { }
}
Aksoyn answered 27/10, 2008 at 19:30 Comment(0)
M
8

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.)

Murmur answered 27/10, 2008 at 19:30 Comment(7)
Uh, List<T> does play nice, right? Right? Coz I need to rewrite some stuff if it doesn't.Jin
Of course List<T> plays nice. And use SortedDictionary<T> if the order of items matters to you.Maeda
List<T> maintains the order in which items are added, however when you call GetEnumerator (i.e. in a foreach loop), the enumerator returned is NOT guranteed to preserve order. You would have to loop through by index to gurantee the order when processing.Dichroic
@ck, do you have a link for that? Lot's of people are going to be surprised, I believe...Murmur
@ck: Wrong. List<T>.GetEnumerator does preserve order.Carrack
I think @ck is suggesting that there are no guarantees implied in the contract that the enumeration will happen in order.Damaging
Hmm. This is entirely a hashtable gotcha, not a .Net gotcha.Cardiganshire
L
8

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

Linea answered 27/10, 2008 at 19:30 Comment(4)
I guess this is purely Visual Studio weirdness.Quar
That's pretty funny.. Anton is probably right.Distill
I dont find that a gotcha but a useful feature!Fissile
I think of this as translating English to French then back to English. You often don't end up with exactly what you started with.Quartz
G
7

The base keyword doesn't work as expected when evaluated in a debugging environment: the method call still uses virtual dispatch.

This wasted a lot of my time when I stumbled across it and I thought I'd encountered some kind of rift in the CLR's space-time, but I then realized it's a known (and even somewhat intentional) bug:

http://blogs.msdn.com/jmstall/archive/2006/06/29/funceval-does-virtual-dispatch.aspx

Galvanoscope answered 27/10, 2008 at 19:30 Comment(0)
C
7

The DesignMode property in all UserControls does not actually tell you if you are in design mode.

Chancy answered 27/10, 2008 at 19:30 Comment(0)
B
6

ASP.NET:

If you are using Linq-To-SQL, you call SubmitChanges() on the data context and it throws an exception (e.g. duplicate key or other constraint violation), the offending object values remain in your memory while you are debugging, and will be resubmitted every time you subsequently call SubmitChanges().

Now here's the real kicker: the bad values will remain in memory even if you push the "stop" button in your IDE and restart! I don't understand why anyone thought this was a good idea - but that little ASP.NET icon that pops up in your system tray stays running, and it appears to save your object cache. If you want to flush your memory space, you have to right-click that icon and forcibly shut it down! GOTCHA!

Borough answered 27/10, 2008 at 19:30 Comment(3)
Surely you mean pause, not stop.Giraldo
David - no, I meant what I said. Even if you push "Stop", the little ASP.NET icon keeps on running in your taskbar. That's why it's a gotcha!Borough
+1 because of the first part, no independent knowledge of the kicker.Excellent
L
6

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.

Letty answered 27/10, 2008 at 19:30 Comment(2)
So what happens when you dispose it. You kill the entire site?Hardboard
You lose the reference to the siteLetty
B
5
enum Seasons
{
    Spring = 1, Summer = 2, Automn = 3, Winter = 4
}

public string HowYouFeelAbout(Seasons season)
{
    switch (season)
    {
        case Seasons.Spring:
            return "Nice.";
        case Seasons.Summer:
            return "Hot.";
        case Seasons.Automn:
            return "Cool.";
        case Seasons.Winter:
            return "Chilly.";
    }
}

Error?
not all code paths return a value ...
are you kidding me? I bet all code paths do return a value because every Seasons member is mentioned here. It should have been checking all enum members and if a member was absent in switch cases then such error would be meaningful, but now I should add a Default case which is redundant and never gets reached by code.

EDIT :
after more research on this Gotcha I came to Eric Lippert's nice written and useful post but it is still kind of weird. Do you agree?

Belligerency answered 27/10, 2008 at 19:30 Comment(1)
The default case will be hit by this code HowYouFeel(42);. Yes there is not enum for 42, but it is still valid. .NET does not constrain enum types to the defined values, and value from the base type of the enum (in this case integer) can be passed.Cutty
R
5

Check this one out:

class Program
{
    static void Main(string[] args)
    {
        var originalNumbers = new List<int> { 1, 2, 3, 4, 5, 6 };

        var list = new List<int>(originalNumbers);
        var collection = new Collection<int>(originalNumbers);

        originalNumbers.RemoveAt(0);

        DisplayItems(list, "List items: ");
        DisplayItems(collection, "Collection items: ");

        Console.ReadLine();
    }

    private static void DisplayItems(IEnumerable<int> items, string title)
    {
        Console.WriteLine(title);
        foreach (var item in items)
            Console.Write(item);
        Console.WriteLine();
    }
}

And output is:

List items: 123456
Collection items: 23456

Collection constructor that accepts IList creates a wrapper around original List, while List constructor creates a new List and copies all references from original to the new List.

See more here: http://blog.roboblob.com/2012/09/19/dot-net-gotcha-nr1-list-versus-collection-constructor/

Reticulation answered 27/10, 2008 at 19:30 Comment(1)
Oh my GOD.. thats terrible from BCLFissile
B
5

Oracle parameters have to added in order

This is a major gotcha in the ODP .Net implementation of parameterized queries for Oracle.

When you add parameters to a query, the default behavior is that the parameter names are ignored, and the values are used in the order in which they were added.

The solution is to set the BindByName property of the OracleCommand object to true - it's false by default... which is qualitatively (if not quite quantitatively) something like having a property called DropDatabaseOnQueryExecution with a default value of true.

They call it a feature; I call it a pit in the public domain.

See here for more details.

Borough answered 27/10, 2008 at 19:30 Comment(1)
Same for Access, my question here #7166161Fissile
C
5

For both LINQ-to-SQL and LINQ-to-Entities

return result = from o in table
                where o.column == null
                select o;
//Returns all rows where column is null

int? myNullInt = null;
return result = from o in table
                where o.column == myNullInt
                select o;
//Never returns anything!

There's a bug-report for LINQ-to-Entites here, though they don't seem to check that forum often. Perhaps someone should file one for LINQ-to-SQL as well?

Chancy answered 27/10, 2008 at 19:30 Comment(1)
For those who found this page due to encountering this very bug, a workaround can be found hereChancy
S
5

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.

Superimpose answered 27/10, 2008 at 19:30 Comment(6)
I used to work with some ex-Java guys who loved to override anything they could get their hands on, and they were constantly screwing each other up with this one. I always used ==, so nothing they did ever affected me.Jin
I thought you could overload the == operator as well...Volsung
You can override the == operator, but overriding the .Equals() won't do it for you. So you could hypothetically override both .Equals() and ==, and have them do different things :\Superimpose
You can override Equals and overload ==. The difference is subtle but very important. More info here. #1766992Brinker
I seem to remember along time ago that == is the value comparison and the .Equals() is the object comparitor. And also if you override the Equals() you have to override the GetHashCode()...not sure. (I may be wrong in that)Ewer
Another gotcha about equality is that you cannot override == for interfaces. Even if your classes have a custom Equals, == cannot be told to use it, and will only be able to compare the reference.Cardiganshire
O
5

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.

Oath answered 27/10, 2008 at 19:30 Comment(3)
I constantly type lowercase datetime all the time... luckily intellisense fix it for me :-)Oudh
Why should this matter? DateTime is immutable anyway and I can't see a situation where you actually need to know if it is a reference type or not.Skald
Yes, needs reminding. The most frequent annoyance I've had related to this is that the legacy application I maintain has DateTimes right left and center, and they may or may not unset, uninitialized, valid, etc, and of course I want to see if they are "null", which of course in the DB they frequently are,but can't be in the code. So they have to be DateTime.MinValue or some such magic number or other Kludge, and I have to make sure they are if they have not been absolutely & for sure set to something at least plausible.Backstitch
M
4

The recursive property gotcha

Not specific to C#, I think, and I'm sure I've seen it mentioned elsewhere on SO (this is the question that reminded me of it)

It can happen two ways, but the end result is the same:

Forgetting to reference base. when overriding a property:

 public override bool IsRecursive
 {
     get { return IsRecursive; }
     set { IsRecursive = value; }
 }

Changing from auto- to backed- properties, but not quite going all the way:

public bool IsRecursive
{
    get { return IsRecursive; }
    set { IsRecursive = value; }
}
Murmur answered 27/10, 2008 at 19:30 Comment(2)
Not really a gotcha, just careless coding.Borough
Is there a question for that? :)Murmur
C
4

VisibleChanged is not usually called when Visible changes.

Chancy answered 27/10, 2008 at 19:30 Comment(1)
It seems a more fundamental problem is that "Visible" has different meanings in its getter and setter. The "Visible" property should indicate whether the control should 'try' to show itself, and a separate "CanBeSeen" property should be the 'and' if a control's own "visible" property and its parent's "CanBeSeen".Boden
B
3

Linq-To-Sql and the database/local code ambiguity

Sometimes Linq just can't work out whether a certain method is meant to be executed on the DB or in local code.

See here and here for the problem statement and the solution.

Borough answered 27/10, 2008 at 19:30 Comment(1)
See also: stackoverflow.com/questions/2675536Chancy
R
3

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

Link

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

Radley answered 27/10, 2008 at 19:30 Comment(0)
P
2

I always thought value types were always on stack and reference types on heap.

Well it is not so. When i saw this question recently on SO (and arguably answered incorrectly) i came to know its not the case.

As Jon Skeet answered (giving a reference to Eric Lippert's Blog post ) its a Myth.

Considerably Important Links:

The truth about Value Types

References are not aAddress

The Stack is an Implementation Detail Part 1

The Stack is an Implementation Detail Part 2

Pogonia answered 27/10, 2008 at 19:30 Comment(1)
But how is this important when coding? Memory is managed by the framework. Most of the time, you don't have to care.Skald
Z
2

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!

Zloty answered 27/10, 2008 at 19:30 Comment(0)
B
2

LINQ to SQL and One-To-Many Relationships

This is a lovely one that has bitten me a couple times, and MS left it to one of their own developers to put it in her blog. I can't put it any better than she did, so take a look there.

Borough answered 27/10, 2008 at 19:30 Comment(0)
W
1

Passing a capacity to List<int> instead of using the collection initializer.

var thisOnePasses = new List<int> {2}; // collection initializer
var thisOneFails = new List<int> (2);  // oops, use capacity by mistake #gotcha#

thisOnePasses.Count.Should().Be(1);
thisOnePasses.First().Should().Be(2);

thisOneFails.Count.Should().Be(1);     // it's zero
thisOneFails.First().Should().Be(2);   // Sequence contains no elements...
Wobble answered 27/10, 2008 at 19:30 Comment(4)
Your statements regarding thisOneFails are incorrect. Count will be 0, and thus .First() will fail. (As a side note, they also don't showcase the behavior mentioned at the beginning of your post)Linalool
thisOneFails is meant to failWobble
I apologize. I found the layout of the example awkward: thisOneFails is failing in 2 different places (.Be(1) in the first statement and .First() in the second statement).Linalool
ty for taking the time to reply :)Wobble
S
1

Not the worst, but one that hasn't been brought up yet. Factory methods passed as arguments to System.Collections.Concurrent methods can be called multiple times even if only one return value is ever used. Considering how strongly .NET tries to protect you from spurious wake-up in threading primitives this can come as a surprise.

using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ValueFactoryBehavingBadlyExample
{
    class Program
    {
        static ConcurrentDictionary<int, int> m_Dict = new ConcurrentDictionary<int, int>();
        static ManualResetEventSlim m_MRES = new ManualResetEventSlim(false);
        static void Main(string[] args)
        {
            for (int i = 0; i < 8; ++i)
            {
                Task.Factory.StartNew(ThreadGate, TaskCreationOptions.LongRunning);
            }
            Thread.Sleep(1000);
            m_MRES.Set();
            Thread.Sleep(1000);
            Console.WriteLine("Dictionary Size: " + m_Dict.Count);
            Console.Read();
        }

        static void ThreadGate()
        {
            m_MRES.Wait();
            int value = m_Dict.GetOrAdd(0, ValueFactory);
        }

        static int ValueFactory(int key)
        {
            Thread.Sleep(1000);
            Console.WriteLine("Value Factory Called");
            return key;
        }
    }
}

(Possible) Output:

Value Factory Called
Value Factory Called
Value Factory Called
Value Factory Called
Dictionary Size: 0
Value Factory Called
Value Factory Called
Value Factory Called
Value Factory Called
Semeiology answered 27/10, 2008 at 19:30 Comment(0)
M
1

Sometimes the line numbers in the stack trace do not match the line numbers in the source code. This might happen due to inlining of simple(single-line) functions for optimization. This is a serious source of confusion for people debugging using logs.

Edit: Example: Sometimes you see a null reference exception in the stack trace where it points to a line of code with absolutely no chance of null reference exception, like a simple integer assignment.

Marshy answered 27/10, 2008 at 19:30 Comment(0)
B
1

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!

Borough answered 27/10, 2008 at 19:30 Comment(4)
No, this is just you being silly. How is .Sum() supposed to return null when you’re using decimal as the static type?Quintanilla
@Quintanilla I would expect it to return 0, to be quite honest. If I'm not mistaken, it returns 0 for in-memory LINQ queries. Plus, it makes sense logicallyObtect
@Rob: It does for Sum, yeah — I grant you that — but none of the other aggregate functions (Average, Min and Max).Quintanilla
Actually the issue I have with this answer is that it misdiagnoses the problem and misphrases the solution. The problem is not that “the SQL query doesn’t have a data type”, and the solution is not to “tell Linq that it’s a decimal”. The problem is that the SQL query returns null and the solution is to tell Linq to use a nullable type.Quintanilla
C
1

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://mcmap.net/q/86891/-what-is-the-worst-gotcha-in-c-or-net-closed). The property getter on that answer is essentially doing the exact same thing as the above code and crashing with no stack trace.

Concenter answered 27/10, 2008 at 19:30 Comment(2)
Once the stack overflows, no more code can execute, including the exception handler. I'm not sure this is really a gotcha, but I guess it could be confusing if you weren't used to the idea.Bronchia
This is just bad code, not a gotcha.Borough
F
1
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.

Fearless answered 27/10, 2008 at 19:30 Comment(3)
It's immutable; myString is not changed, it returns a new string where 'x' has been replaced by 'y'.Dinh
A good convention (unfortunately not used much in .net) is to use two patterns: "string GetReplaced()" for a call that returns a copy and "void Replace()" for methods that alter the original object. The distinct behaviours/usages are then rather difficult to mix up.Aesthetic
Ditto? What do you mean? (I know you mean that .Replace returns the modified string, but does not change mystring. You really should edit your answer so that it stands alone).Lymph
B
0

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.

Borough answered 27/10, 2008 at 19:30 Comment(0)
B
0

LinqToSql batches get slower with the square of the batch size

Here's the question (and answer) where I explored this problem.

In a nutshell, if you try to build up too many objects in memory before calling DataContext.SubmitChanges(), you start experiencing sluggishness at a geometric rate. I have not confirmed 100% that this is the case, but it appears to me that the call to DataContext.GetChangeSet() causes the data context to perform an equivalence evaluation (.Equals()) on every single combination of 2 items in the change set, probably to make sure it's not double-inserting or causing other concurrency issues. Problem is that if you have very large batches, the number of comparisons increases proportionately with the square of n, i.e. (n^2+n)/2. 1,000 items in memory means over 500,000 comparisons... and that can take a heckuva long time.

To avoid this, you have to ensure that for any batches where you anticipate large numbers of items, you do the whole thing within transaction boundaries, saving each individual item as it is created, rather than in one big save at the end.

Borough answered 27/10, 2008 at 19:30 Comment(0)
E
-1

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.

Exclamatory answered 27/10, 2008 at 19:30 Comment(2)
That should be somewhat obvious... q1 has to check the entire list to ensure there is only 1 match.Ani
I think a lot of people don't know about .First()Oneal

© 2022 - 2024 — McMap. All rights reserved.