---
title: "Being stupid is fun: Let's create long exception messages!"
description: "Sometimes doing arbitrary stuff feels just good! So let's do that!"
updated: 2026-02-23
tags: ["C#"]
canonical: "https://steven-giesel.com/blogPost/63431000-1192-4210-a85c-5fff00910d93/being-stupid-is-fun-lets-create-long-exception-messages"
---

# Being stupid is fun: Let's create long exception messages!

Sometimes doing arbitrary stuff feels just good! So let's do that!

---

Spoiler alert and disclaimer: This blog post will generate 0 value in your life. Be warned that you lose precious lifetime without getting anything back!

---

## `new string(char c, int count)`

If we create a string, we are allowed to pass in an `int` count. So naturally, I would do something like:

```csharp
string longMessage = new('X', int.MaxValue);
```

But we are greeted with a:

> Unhandled exception. System.OverflowException: Arithmetic operation resulted in an overflow.

Well internally `new string` calls a method called `FastAllocateString`, which is defined as:
```csharp
internal static extern unsafe string FastAllocateString(MethodTable *pMT, nint length);
```

So basically the CLR, Common Language Runtime, handles that call ... and fails. Why is that? For starters a single object is not allowed to be larger than 2 GB. And if we oversimplify `string` it is a `char[]` (again we are oversimplifying - the reality looks a bit different). A `char` takes two bytes. So 2 bytes * `int.MaxValue` is roundabout 4 GB! So that won't work. So at least we have to take `new('X', int.MaxValue / 2);` But hold on, there is more (empirically try it out and get greeted by the same exception): Every object has sometimes additional metadata (like `List<T>` has `Count` which takes 4 bytes and so `string` has also an `int Length`) and some headers for the runtime itself so it can keep track of objects (like `MethodTable`, we also have the terminating `'\0'`, Sync Block Index - for `lock` and friends - and so on). 

If we do:
```csharp
string longMessage = new('X', int.MaxValue / 2 - 32);
````

Then we are looking at success! And thanks to recent development price-wise for memory, you can feel rich! Okay. Moving on: Going on to the next part:

```csharp
string longMessage = new('X', int.MaxValue / 2 - 32);

throw new Exception(longMessage);
```

That results in:
> Cannot print exception string because Exception.ToString() failed.

That makes sense. If we have a look at a basic `Exception.ToString`:
```csharp
public override string ToString()
{
    string className = GetClassName();
    string? message = Message;
    string innerExceptionString = _innerException?.ToString() ?? "";
    string endOfInnerExceptionResource = SR.Exception_EndOfInnerExceptionStack;
    string? stackTrace = StackTrace;

    // Calculate result string length
    int length = className.Length;
    checked
    {
        if (!string.IsNullOrEmpty(message))
        {
            length += 2 + message.Length;
        }
        if (_innerException != null)
        {
            length += Environment.NewLineConst.Length + InnerExceptionPrefix.Length + innerExceptionString.Length + Environment.NewLineConst.Length + 3 + endOfInnerExceptionResource.Length;
        }
        if (stackTrace != null)
        {
            length += Environment.NewLineConst.Length + stackTrace.Length;
        }
    }

    // Create the string
    string result = string.FastAllocateString(length);
    Span<char> resultSpan = new Span<char>(ref result.GetRawStringData(), result.Length);

    // Fill it in
    Write(className, ref resultSpan);
    if (!string.IsNullOrEmpty(message))
    {
        Write(": ", ref resultSpan);
        Write(message, ref resultSpan);
    }
    if (_innerException != null)
    {
        Write(Environment.NewLineConst, ref resultSpan);
        Write(InnerExceptionPrefix, ref resultSpan);
        Write(innerExceptionString, ref resultSpan);
        Write(Environment.NewLineConst, ref resultSpan);
        Write("   ", ref resultSpan);
        Write(endOfInnerExceptionResource, ref resultSpan);
    }
    if (stackTrace != null)
    {
        Write(Environment.NewLineConst, ref resultSpan);
        Write(stackTrace, ref resultSpan);
    }
    Debug.Assert(resultSpan.Length == 0);

    // Return it
    return result;

    static void Write(string source, ref Span<char> dest)
    {
        source.CopyTo(dest);
        dest = dest.Slice(source.Length);
    }
}
```

It does add a lot to our string and calls still that `FastAllocateString` stuff - we barely could create it and `ToString` is overboarding us with too much stuff! Okay we have two ways out:
 * Giving it a bit of space
 * Creating our own type that doesn't do that stuff!

```csharp
string longMessage = new('X', int.MaxValue / 10 - 32);

throw new MyException(longMessage);

public class MyException : Exception
{
    public MyException(string message)
    {
        Message = message;
    }

    public override string Message { get; }

    public override string ToString()
    {
        return Message;
    }
}
```

Now we are looking at many `X` on the screen! Task accomplished.

