C#

Whats the strangest corner case youve seen in C or NET closed

25 September 2026 · 7 min read

Whats the strangest corner case youve seen in C or NET closed

C and .NET, while robust and versatile, have their quirks. Developers, especially those who’ve been wrestling with the frameworks for years, often stumble upon unexpected behaviors that defy logic and challenge assumptions. These “corner cases,” as they’re affectionately called, can range from mildly annoying to downright baffling. This post explores some of the strangest corner cases encountered in C and .NET, offering insights into their causes and potential solutions. Prepare to delve into the fascinating world of unexpected behavior in these popular programming environments.

Floating-Point Fun

Floating-point arithmetic is notorious for its inherent imprecision. While this is not specific to C or .NET, it can lead to surprising results. For instance, a simple comparison like 0.1 + 0.2 == 0.3 evaluates to false. This stems from the way floating-point numbers are represented in binary, leading to tiny rounding errors that accumulate. Developers must be mindful of these limitations and use appropriate techniques, such as epsilon comparisons or dedicated decimal types, for accurate calculations.

Consider this scenario: you’re calculating the total cost of items in a shopping cart. Using floating-point numbers directly can lead to discrepancies in the final sum. Switching to the decimal type, which offers higher precision for financial calculations, can mitigate this risk. As Microsoft’s documentation highlights, “The decimal type is appropriate for financial calculations that require a high degree of precision.”

String Interning Surprises

String interning, an optimization technique used by .NET, can sometimes lead to unexpected behavior. Strings are immutable, and the runtime environment often reuses the same memory location for identical string literals to save space. This can create situations where comparing string references (object.ReferenceEquals) returns true even for strings created separately. While generally beneficial for performance, interning can cause confusion when developers rely on reference equality for string comparisons.

Imagine comparing two strings representing user input. If these strings happen to be interned, a reference comparison might incorrectly indicate they are the same object, even if they originated from different sources. Understanding string interning is crucial to avoid such pitfalls.

Generic Type Variance Gotchas

Generic type variance, introduced in C 4.0, allows for more flexible type assignments with generics. However, it can also introduce subtle bugs if not understood thoroughly. Covariance and contravariance, the two aspects of variance, dictate how generic types with different type arguments relate to each other. Incorrectly applying variance can lead to runtime exceptions when assigning incompatible types.

For instance, assuming IEnumerable<string> is assignable to IEnumerable<object> because string derives from object is a common mistake. This is not allowed without explicit covariance. Understanding the rules of variance is essential for writing type-safe generic code.

Here’s a simplified example:

  • Covariance: IEnumerable<string> is assignable to IEnumerable<object> (read-only)
  • Contravariance: Action<object> is assignable to Action<string> (write-only)

Async/Await Weirdness

The async and await keywords simplified asynchronous programming in C, but they can still introduce unexpected behavior if not used carefully. One common issue is deadlock scenarios, which can occur when an await call is made within a synchronous context that implicitly holds a lock. This can block the asynchronous operation indefinitely.

Consider calling an asynchronous method from within a UI event handler. If the asynchronous method attempts to access UI elements directly, it can lead to a deadlock because the UI thread is already blocked waiting for the asynchronous operation to complete. Properly configuring the synchronization context is crucial to avoid such deadlocks.

Here are some steps to avoid async/await deadlocks:

  1. Understand the synchronization context.
  2. Use ConfigureAwait(false) where appropriate.
  3. Avoid blocking calls within asynchronous methods.

Another puzzling behavior can arise from the capturing of local variables within asynchronous methods. Modifications to captured variables within the asynchronous method might not be reflected as expected in the calling method due to the asynchronous execution flow.

Learn more about asynchronous programming best practices.“Asynchronous programming is a powerful tool, but it requires a deep understanding of its intricacies to avoid unexpected behavior,” says renowned C expert, [Expert Name].

FAQ

Q: What are the common causes of corner cases in C and .NET?

A: Common causes include floating-point imprecision, string interning, generic type variance complexities, and asynchronous programming subtleties.

These are just a few examples of the many strange corner cases that developers might encounter in C and .NET. While they can be frustrating, understanding their underlying causes and adopting best practices can help mitigate their impact. Continuous learning and a thorough understanding of the framework’s intricacies are essential for navigating the sometimes-perplexing world of C and .NET development. Explore resources like Stack Overflow and Microsoft’s documentation to delve deeper into specific issues and learn from the experiences of other developers. By staying informed and adopting a proactive approach, you can effectively tackle these challenges and build robust and reliable applications.

[Infographic illustrating common C/.NET corner cases and their solutions]

Question & Answer :

I collect a few corner cases and [brain teasers](http://www.yoda.arachsys.com/csharp/teasers.html) and would always like to hear more. The page only really covers C# language bits and bobs, but I also find core .NET things interesting too. For example, here's one which isn't on the page, but which I find incredible:
string x = new string(new char[0]); string y = new string(new char[0]); Console.WriteLine(object.ReferenceEquals(x, y)); 

I’d expect that to print False - after all, “new” (with a reference type) always creates a new object, doesn’t it? The specs for both C# and the CLI indicate that it should. Well, not in this particular case. It prints True, and has done on every version of the framework I’ve tested it with. (I haven’t tried it on Mono, admittedly…)

Just to be clear, this is only an example of the kind of thing I’m looking for - I wasn’t particularly looking for discussion/explanation of this oddity. (It’s not the same as normal string interning; in particular, string interning doesn’t normally happen when a constructor is called.) I was really asking for similar odd behaviour.

Any other gems lurking out there?

I think I showed you this one before, but I like the fun here - this took some debugging to track down! (the original code was obviously more complex and subtle…)

static void Foo<T>() where T : new() { T t = new T(); Console.WriteLine(t.ToString()); // works fine Console.WriteLine(t.GetHashCode()); // works fine Console.WriteLine(t.Equals(t)); // works fine // so it looks like an object and smells like an object... // but this throws a NullReferenceException... Console.WriteLine(t.GetType()); } 

So what was T…

Answer: any Nullable<T> - such as int?. All the methods are overridden, except GetType() which can’t be; so it is cast (boxed) to object (and hence to null) to call object.GetType()… which calls on null ;-p


Update: the plot thickens… Ayende Rahien threw down a similar challenge on his blog, but with a where T : class, new():

private static void Main() { CanThisHappen<MyFunnyType>(); } public static void CanThisHappen<T>() where T : class, new() { var instance = new T(); // new() on a ref-type; should be non-null, then Debug.Assert(instance != null, "How did we break the CLR?"); } 

But it can be defeated! Using the same indirection used by things like remoting; warning - the following is pure evil:

class MyFunnyProxyAttribute : ProxyAttribute { public override MarshalByRefObject CreateInstance(Type serverType) { return null; } } [MyFunnyProxy] class MyFunnyType : ContextBoundObject { } 

With this in place, the new() call is redirected to the proxy (MyFunnyProxyAttribute), which returns null. Now go and wash your eyes!