DEV Community

Cover image for C# .NET Top 10 Compiler and .NET environment limits
chrdek
chrdek

Posted on • Updated on

C# .NET Top 10 Compiler and .NET environment limits

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

 
 


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.

 
.NET memory limits
RedGate - memory allocation
 


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:\some_256_character_path_string<NUL>
Enter fullscreen mode Exit fullscreen mode

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

 
MS - File path limits
 


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.

 
MS - Inheritance
Multi-Inheritance Example
 


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:

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);
}                               |     }
Enter fullscreen mode Exit fullscreen mode

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.

 
Tail Recursion, Trampoline lib example
Stack Limits, other
Differences, code
 


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:
 

ValueTuple<T1,T2,T3,T4,T5,T6,T7>(T1, T2, T3, T4, T5, T6, T7)
Enter fullscreen mode Exit fullscreen mode

 
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.

 
MS - Tuples
Tuple examples
 


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:

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();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

 
MS - IDisposable, Dispose-Pattern
IDisposable examples
 


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.

 
MS - Garbage collection fundamentals
MS - Garbage collection (LOH,SOH)
 


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.

 
MS - NewtonSoft.Json migration
Relevant git issue
 


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

 
Thread Pool Example
 


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

 
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)

 
MS - Numeric max-min sizes
MS - Numeric ValueTypes
Numeric types discussion
 

Bonus #1:
6 different ways of declaring and combining string values inside another string in .NET.
1.

//string interpolation with $"" prefixing:
string name = "John"; string greeting = $"Hello, {name}!";
Enter fullscreen mode Exit fullscreen mode

2.

//string.Format Method and {0},{1},{2} templated string placeholders:
string name = "John"; string greeting = string.Format("Hello, {0}!", name);
Enter fullscreen mode Exit fullscreen mode

3.

//Direct '+' operator string concatenation or by using '+=' operator: 
string name = "John"; string greeting = "Hello, " + name + "!";
Enter fullscreen mode Exit fullscreen mode

4.

//String.Concat Method
string firstName = "John"; string lastName = "Doe"; string fullName = string.Concat(firstName, " ", lastName);
Enter fullscreen mode Exit fullscreen mode

5.

//StringBuilder Append Method for appending bulk lines of text:
StringBuilder sb = new StringBuilder(); sb.Append("Hello, "); sb.Append("World!"); string result = sb.ToString();
Enter fullscreen mode Exit fullscreen mode

6.

//string.Join Method:
string[] parts = { "Hello", name }; string result = string.Join(" ", parts);
Enter fullscreen mode Exit fullscreen mode

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.

 
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.

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

 
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.

 
MS - Garbage collection fundamentals
MS - Garbage collection (LOH,SOH)
Other Gen0-Gen2 examples

 
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.

 
Discussion with examples
 

Bonuses section completes the list of the most important features of in-IDE C# usage for the majority of .NET programmers.

 

UPDATE: Included also a PDF CheatSheet for direct download here

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!

Top comments (0)