C# .NET Top 10 Compiler and .NET environment limits
Tags: csharp, dotnet, programming, visualstudio
Published: 2023-12-08 13:53:18

---
title: C# .NET Top 10 Compiler and .NET environment limits
published: true
description: 
tags: Csharp, dotnet, programming, visualstudio
cover_image: https://www.simplilearn.com/ice9/free_resources_article_thumb/Csharp_Programming_for_Beginners.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2023-12-07 12:56 +0000
---

After a few months of development work and some personal projects of mine (including 100s of deployments), I've set a part of my projects site, that is based on C# projects.

The entire process and relevant setup led to this post which includes a list of basic C# usage limits also-known-as  __the top 10 things to note when writing or testing code in your IDE with C#.__

&nbsp;
&nbsp;
<hr/>
__Limitation #1:__
`Int32.MaxValue` 2^31 characters for string data types. This limitation is set mainly due to the CLR memory of .NETs runtime compilation environment. Similarly, the maximum number of elements that can be stored in the current implementation of `List<T>` is, theoretically, `Int32.MaxValue` (or) just over 2 billion (2^31). In current Microsoft CLR implementations there's a 2GB maximum object size limit. This applies exactly the same as HashTables, Arrays (2d or 1d) and other enumerations, indexed structures.

&nbsp;
[.NET memory limits](https://www.codeproject.com/Articles/483475/Memory-Limits-in-a-NET-Process)
[RedGate - memory allocation](https://www.red-gate.com/simple-talk/development/dotnet-development/object-overhead-the-hidden-net-memory-allocation-cost/)
&nbsp;
<hr/>
__Limitation #2:__
MAX_PATH per directory's path of 260 characters. This includes ` "C:\" ` part prepended, which is followed by 256 string style ` '\' ` characters and a null terminating character.

The entire sequence of MAX_PATH in-stack is allocated as below:
``` C#
C:\some_256_character_path_string<NUL>
```

This is both an OS and .NET imposed limitation since the .NET-based IDE sets it self up to the OS directory limits. Currently, this is applied from the CLR - for up to Windows 10 (1607) and newer versions.
__NOTE:__ This is a Win32 relevant IDE limitation on Windows built-in API which can also be partially tweaked via the Windows registry (not recommended).

&nbsp;
[MS - File path limits](https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry)
&nbsp;
<hr/>
__Limitation #3:__
_Multiple inheritance_ in C# by using classes and subclasses is not directly supported, similar behavior is applied by implementing one interface per class and then sub-classing for every other class that requires each set of interface properties.
__Not to be confused with multiple class styles (aka polymorphism),__ C# cannot allow one class inheriting directly from multiple abstract or standard classes.
This achieves simplicity and also mitigates the __'diamond problem'__ which involves ambiguous method calls that occur when a subclass inherits from two classes that share common method namings but have different behavior.


Explicit interface implementation, allows secure design of the class hierarchy to avoid such conflicts.
On the other hand standard class polymorphism is allowed by utilizing class overrides -as normal- by usage of `virtual` `abstract` keyword combinations between normal and abstract classes.
Also: In C#, `struct` does not support polymorphism directly but only via interface usage.


&nbsp;
[MS - Inheritance](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/object-oriented/inheritance)
[Multi-Inheritance Example](https://www.geeksforgeeks.org/c-sharp-multiple-inheritance-using-interfaces/)
&nbsp;
<hr/>
__Limitation #4:__
Tail recursion is not fully supported due to CLR limitations. (CLR allows no more than 100,000 nested function calls before stack resulting in runtime `StackOverflowException` Error).
Provided that `int.MaxValue` is around 2.1 billion, most likely your program will never return or will give out a stack exception before reaching the global CLR maximum.

Nonetheless, this can be mitigated by Bounce function support. This is implemented in-code by including Trampoline library. An alternative optimization would be to allow Head Recursive calls instead.

An example of tail/head call is displayed below:
```C#
public void tail(int n)         |     public void head(int n)
{                               |     {
    if(n == 1)                  |         if(n == 0)
        return;                 |             return;
    else                        |         else
  System.Console.WriteLine(n);  |             head(n-1);
                                |
    tail(n-1);                  |     System.Console.WriteLine(n);
}                               |     }
```
Another method of optimizing tail recursion so that the compiler does not run out of memory is temporary storing function calls during recurrent stack allocation (aka memoization). This reduces function calls so that methods apply to wider range of values with less error margin.

&nbsp;
[Tail Recursion, Trampoline lib example](https://thomaslevesque.com/2011/09/02/tail-recursion-in-c/)
[Stack Limits, other](https://stackoverflow.com/questions/23132424/self-recursive-function-limits)
[Differences, code](https://stackoverflow.com/questions/21426688/the-difference-between-head-tail-recursion)
&nbsp;
<hr/>
__Limitation #5:__
C# tuples support a maximum of 7 different data types of elements-by-value per data set for various data types (C# with .NET v4.0 and above).
Constructor definition of ValueTuple displayed below:
&nbsp;
```C#
ValueTuple<T1,T2,T3,T4,T5,T6,T7>(T1, T2, T3, T4, T5, T6, T7)
```
&nbsp;
NOTE: For more than 7 different data types use TRest where TRest is a generic ValueType tuple instance declaring the rest of elements in sequence.

&nbsp;
[MS - Tuples](https://learn.microsoft.com/en-us/dotnet/api/system.valuetuple-8?view=net-8.0)
[Tuple examples](https://www.pluralsight.com/guides/working-tuples-csharp)
&nbsp;
<hr/>
__Limitation #6:__
Some .NET CLR objects that do **not directly implement** IDisposable:
__`StringBuilder`, `Stream`, `TcpClient`, `MemoryStream`, `RegistryKey`__

StringBuilder is used for efficient string manipulation. Since it doesn't implement IDisposable, it's important to dispose its resources, especially in scenarios where large strings are involved, to release memory.

In order to reallocate memory in CLR un-managed data structures with manual garbage collection in the heap while running a program GC.Collect() is called manually for the mentioned cases of .NET data types. 
Such an example, _**using Dispose pattern**_ is  displayed in the MS code block below:

```C#
public class DisposableResourceHolder : IDisposable {

    private SafeHandle resource; // handle to a resource

    public DisposableResourceHolder() {
        this.resource = ... // allocates the resource
    }

    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing) {
        if (disposing) {
            if (resource!= null) resource.Dispose();
        }
    }
}
```
Overall, objets with unsupported garbage collection capability of .NET are mainly but not limited to Win32 file handling wrappers that partially require OS resources.

Alternatively, some .NET CLR objects that **directly implement** IDisposable:
__`SqlConnection`, `SqlCommand`__

Overall most core data types in C# implement IDisposable. This design allows proper cleanup of resources such as closing file handles or network connections. The precise count varies with language updates and includes widely-used types and methods.

Best approach when dealing with objects directly inheriting from `IDisposable` is within `using(){ }` blocks.

&nbsp;
[MS - IDisposable, Dispose-Pattern](https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/dispose-pattern)
[IDisposable examples](https://stackoverflow.com/questions/18336856/implementing-idisposable-correctly)
&nbsp;
<hr/>
__Limitation #7:__
Minimum size for single large objects memory allocation, in large object heap allows for **objects of up to 85,000 bytes and above.** These are usually arrays used in a program etc.

Entire heap is separated in: large object heap (LOH) and the small object heap (SOH) where GC takes place during the lifespan of a program.

Overall, each object's size smaller than 85Kbytes of maximum object heap gets written to SOH whereas sizes larger or equal to 85Kbytes in size are written to the LOH instead.

&nbsp;
[MS - Garbage collection fundamentals](https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals)
[MS - Garbage collection (LOH,SOH)](https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/)
&nbsp;
<hr/>

__Limitation #8:__
This is a maximum limit that targets .NET 6.0 and above.
`System.Text.Json` Namespace Serialization Objects of UTF-8 encoding Json Writer's `Utf8JsonWriter` method for tokenized plaintext input has a default __maximum of 166 MB__. For its base-64 encoded counterpart __125MB.__

&nbsp;
[MS - NewtonSoft.Json migration](https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/migrate-from-newtonsoft?pivots=dotnet-6-0)
[Relevant git issue](https://github.com/dotnet/runtime/issues/30746)
&nbsp;
<hr/>
__Limitation #9:__
`ThreadPool.SetMaxThreads` and `ThreadPool.SetMinThreads` are used to set maximum and minimum number of threads on demand as needed in a thread pool. By default, minimum number of threads in C# is analogous to the number of processors on a system. This applies to logical or physical processing units for multi-core CPUs. 
(4 thread min per 4-core CPU, 2 thread min per 2-core CPU etc.)

&nbsp;
[Thread Pool Example](https://www.c-sharpcorner.com/article/thread-pool-in-net-core-and-c-sharp/)
&nbsp;
<hr/>
__Limitation #10:__
Number of numerical types adjacency groups for compile-time ValueType bindings. ValueType compiler boxing of values copied from method arguments or from one variable to another have the following type relevance as shown below:

**_Group 1._** __`SByte` <-> `Int16` <-> `Int32` <-> `Int64` <-> `Byte` <-> `UInt16` <-> `UInt32` <-> `UInt64` <-> `BigInteger`__
**_Group 2._** __`float` <-> `double` <-> `Decimal`__

&nbsp;
Also Max-Min limits for numerical ValueTypes mentioned are as below

| Type | Max-Min values |
| ---- | --------------- |
| sbyte | -128 to 127 |	
| byte | 0 to 255 |
| short | -32,768 to 32,767 |
| ushort | 0 to 65,535 |
| int | -2,147,483,648 to 2,147,483,647	|
| uint | 0 to 4,294,967,295 |
| long | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807	|
| ulong | 0 to 18,446,744,073,709,551,615 |
| nint | Depends on platform (computed at runtime) |
| nuint | Depends on platform (computed at runtime) |


&nbsp;
[MS - Numeric max-min sizes](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/integral-numeric-types)
[MS - Numeric ValueTypes](https://learn.microsoft.com/en-us/dotnet/api/system.valuetype?view=net-8.0&redirectedfrom=MSDN)
[Numeric types discussion](https://stackoverflow.com/questions/828807/what-is-the-base-class-for-numeric-value-types)
&nbsp;

Bonus #1:
6 different ways of declaring and combining string values inside another string in .NET.
**1.** ```C#
//string interpolation with $"" prefixing:
string name = "John"; string greeting = $"Hello, {name}!";
```
**2.** ```C#
//string.Format Method and {0},{1},{2} templated string placeholders:
string name = "John"; string greeting = string.Format("Hello, {0}!", name);
```
**3.** ```C#
//Direct '+' operator string concatenation or by using '+=' operator: 
string name = "John"; string greeting = "Hello, " + name + "!";
```
**4.** ```C#
//String.Concat Method
string firstName = "John"; string lastName = "Doe"; string fullName = string.Concat(firstName, " ", lastName);
```
**5.** ```C#
//StringBuilder Append Method for appending bulk lines of text:
StringBuilder sb = new StringBuilder(); sb.Append("Hello, "); sb.Append("World!"); string result = sb.ToString();
```
**6.** ```C#
//string.Join Method:
string[] parts = { "Hello", name }; string result = string.Join(" ", parts);
```
__NOTE:__ String methods are not recommended for setting SQL statements in code, refrain from using it as such and use properties instead.
In case you need to do it, the most secure ways of handling it are: `String.Format`, String interpolation, StringBuilder's `Append()` methods.

&nbsp;
Bonus #2:
`T` can be applied to a set of enumerable data sources, including but not limited-to:
- In-memory collections such as Generics.
- Arrays
- Other types implementing IEnumerable<T>.

`TSource` can be applied to directly dynamically allocated data sources typically used within Visual Studio.

TSource is tailored-made for data sources that implement IQueryable, which typically include database queries and other queryable providers (EntityFramework Entities etc.).

&nbsp;
Bonus #3:
Limitation of 4 object generations in heap space garbage collections.
Heap Objects are garbage-collected in the order displayed below:
Gen0 -> SOH Objects Garbage Collected Objects.
Gen1 -> SOH Objects Garbage Collected Objects.
Gen2,3 -> LOH Objects Garbage Collected Objects.

&nbsp;
[MS - Garbage collection fundamentals](https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals)
[MS - Garbage collection (LOH,SOH)](https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/)
[Other Gen0-Gen2 examples](https://stackoverflow.com/questions/41521919/how-garbage-collection-decides-generation-for-variable)

&nbsp;
Bonus #4:
Recommended case limitations of when to use enumeration yields or `yield return`.
- **Case 1:** Do not use within enumeration sequence loops with `foreach( var i in items)`, since it causes arbitrary calls to `MoveNext()` per element, causing uncessecary overhead.

- **Case 2:** Only use per lazily evaluated values. Do not use for specific items that need to be sequentially accessed by number such as array _element[1]_ then _element[2]_ and will need to be processed one after another.

- **Case 3:** Use only when allocating a long sequence (lot of memory) at once and do not require specific elements separately, such as retrieve a list of all items etc..

- **Case 4:** Do not use when you don't want to defer execution state when calling the iterator part. Eg: When throwning an exception then yield returning, causes delay until exception gets thrown properly. This applies to only large yield cases when a delay is noticed before the exception.

&nbsp;
[Discussion with examples](https://stackoverflow.com/questions/3969963/when-not-to-use-yield-return)
&nbsp;
#### Bonuses section completes the list of the most important features of in-IDE C# usage for the majority of .NET programmers.

&nbsp;

__UPDATE: Included also a PDF CheatSheet for direct download [here](https://res.cloudinary.com/dmjcetjt8/image/upload/v1702138892/c_-limits-max_min_s4g1wd.pdf)__

__Note that: this is purely for improving development only and not taken in any other way. In case I have not added something important or not in-line with latest .NET versions drop me a line in the 'Top Comments' section. See you!__