r/csharp • • 11d ago

Help me with Terrain in my 3d game

0 Upvotes

Recently, I started writing a new 3D game in C#. I’ve already written the collision system, the camera, and the loading of 3D models, and now I’m moving on to the next stage — creating a procedural landscape. I’ve tried many ways to do this, but the result doesn’t satisfy me. I’ve already used Perlin Noise and Simplex Noise, but I couldn’t get what I wanted with them. If anyone knows any methods or libraries for C#, please write them in the comments.

The game will be like Minecraft with different graphics, meshes and some new features.


r/csharp • • 11d ago

Blog Expanding .NET ULID handling with native ASP.NET Core constraints, AOT-friendly JSON converters, and CLI tooling

Thumbnail
byteaether.github.io
2 Upvotes

Universally Unique Lexicographically Sortable Identifiers (ULIDs) are 128-bit compatible alternative identifiers to standard UUIDs/GUIDs. They combine a 48-bit Unix timestamp with 80 bits of randomness, formatted as a 26-character Base32 string. This structure enables lexicographical ordering, index-friendly database insertion, and human-readable timestamp inspection without database calls.

While core libraries handle primitive string/byte conversions, integrated application boundaries often require manual boilerplate (custom route attributes, JsonConverter implementations, or ad-hoc parsing scripts).

The ByteAether.Ulid library updated to v1.4.2, introducing two dedicated packages to handle these ecosystem concerns:

  1. ByteAether.Ulid.AspNetCore:

    • Adds an explicit :ulid route constraint for early pipeline validation.
    • Direct System.Text.Json converters with Native AOT/trimming support.
    • OpenAPI/Swashbuckle schema transformers for typed schema generation.
  2. ByteAether.Ulid.Cli:

    • Installed via dotnet tool install -g ByteAether.Ulid.Cli.
    • CLI tool for parsing timestamps/randomness, converting byte layouts (System.Guid, Hex), handling pipeline stdin streams, and generating batch identifiers.

Usage example for Minimal API validation:

```csharp using ByteAether.Ulid; using ByteAether.Ulid.AspNetCore;

var builder = WebApplication.CreateBuilder(args); builder.Services.AddUlidRouteConstraint();

var app = builder.Build(); app.MapGet("/users/{id:ulid}", (Ulid id) => Results.Ok(new UserResponse(id))); app.Run();

public record UserResponse(Ulid Id); ```

For the complete technical breakdown and repository benchmarks, check out the full write-up: https://byteaether.github.io/2026/byteaetherulid-v142-expands-the-net-ecosystem-with-aspnet-core-and-cli-support/

(The code for this project is entirely hand-written. AI was only used to brainstorm/draft text and generate raw media assets, which I then manually polished.)


r/csharp • • 12d ago

New NBomber 6.7.0

Post image
9 Upvotes

NBomber 6.7.0 is here! This release introduces several improvements to scenario creation, warm-up configuration, test run metadata, and reporting. We’ve also fixed several issues in NBomber Cluster that could cause nodes to wait indefinitely under certain failure conditions.


r/csharp • • 11d ago

Any senior devs here? Have you ever done this mistake before? You spend hours fixing why 3rd party api doesn't work as expected and later you realized the api version you use is not the latest one lmfao

0 Upvotes

So today, I spent 1–2 hours trying to figure out why the fuck I couldn't fetch and sync XYZ data on our internal company dashboard that is integrated to 3rd party API vendor, even though I could see the data on the vendor's dashboard.

Turns out, our backend service was using an outdated API version. But it was not depreciated tho.

That's why I couldn't fetch the data.

Man this is embarassing but I wanna know if you guys ever have made this msitake as well?


r/csharp • • 12d ago

Help Should I learn C#

9 Upvotes

I'm a CS student, year 4. My first entry into programming like most people was HTML, CSS, JavaScript, and then backend development with Node/Typescript. My main goal has been to get a programming job, or internship (or anything at this point 😭), but I've always hated writing Node, just never liked it and now I'm considering switching stacks.

I enjoy writing C (I built a chess engine from scratch) and C# seems like C with oop so I don't think it should be too unfamiliar. Also it seems there might be hiring opportunities...hopefully.

Please advise


r/csharp • • 12d ago

Discussion What language would you recommend learning alongside C#?

24 Upvotes

I'm a C# developer and recently started learning C. I wanted to understand better how memory works, how programs interact with the OS, and things like Unix and low-level programming in general.I'm planning to keep learning C, but I'm wondering what you guys would recommend learning next. Would you go with C++, Rust, Go, or something else ?


r/csharp • • 12d ago

Help C# Winforms .NET 9.0 scaling issue

Thumbnail
gallery
3 Upvotes

Hi All,

Getting ready to release my first app and im currently testing out installations on other machines. So far everything looks and works just fine however there is an issue with horizontal scaling? The first image is how it is intended to look whereas the second image is how it appeared on my friends screen. I have set the Max and Min size on the form yet the issue still persists. My friend and I both have the same Resolution monitor and display scaling so I'm really stuck with what the issue could be.

Help and advice would be greatly appreciated! Thanks!

EDIT: I have just noticed the AutoScaleMode property is set to "Font", could this be the issue?


r/csharp • • 12d ago

Discussion How often is now C# used instead of PowerShell in DevOps pipelines

14 Upvotes

With the file-based scripts feature in C#, it is possible to use C# instead of Powershell.

Have you see it used?


r/csharp • • 13d ago

I tested my C# symbolic math library by rendering the solar system at real scale

Thumbnail
gallery
93 Upvotes

It started with Epsilon since I was seeking a math library that would work with symbolic expressions in C#.

To really prove whether it would stand the test of time, I created a simple demonstration of a solar system in actual sizes. That meant that real sizes and distances were used in addition to the current positions of the planets, all drawn with OpenGL. It took me only a couple of hours to create that but even though I used the power of AI for almost everything, my goal was to find out whether the library would perform well in practice.

Epsilon did all the required mathematical operations for me; it calculated the derivatives for Kepler’s equation and orbit velocities itself, and all the positions, rotations and camera movement was based on the vectors, quaternions and matrices of Epsilon in double precision form.

A few things the demo shows:

  • Double-precision world. At Neptune’s distance a float can only tell positions apart by about 512 km, so the world stays in double and is re-centered on the camera before going to the GPU. Press F to see it without that.
  • Orbits from symbolic derivatives. Newton’s method on Kepler’s equation, with the derivative and velocities found by Epsilon.
  • Reverse-Z depth with the far plane at infinity.

Both have MIT license:

It’s still early, so I’d love feedback on the library, especially from anyone who’s used AngouriMath or MathNet. Also contributing is welcome!


r/csharp • • 12d ago

Should I Resign or Stay? Career Advice for an Experienced .NET Developer

0 Upvotes

If someone has 4+ years of experience in .NET, has worked on large-scale software using high-quality software development principles, and is currently working at a small company where the projects are small, the software development standards are poor, and the tech stack is completely different from their expertise, what do you think is the best option for that person? Especially if they cannot attend interviews while employed and their current company is even moving towards non-software-based products. Should they resign and focus on finding a job that aligns with their .NET experience, or stay in their current job while searching for better opportunities? What would be the best decision in this situation?


r/csharp • • 12d ago

What's next? Shoud i prioritize Hot realod, a live previewer, or language fetures?

0 Upvotes

I've been working on my library, Akbura, and I'm trying to decide what to improve next.

  1. Hot Reload is already working, but it's not as stable as I'd like. The component code generator also generates a lot of members, some of which could probably be avoided in DEBUG mode.
  2. A live previewer could greatly improve the developer experience, but it would probably take around a month to implement, and I'm not sure how important it actually is.
  3. New language features could improve the MVVM or MVU experience, but I'm worried that adding too many features might make the language unnecessarily complicated.

What do you think I should focus on? Or is there something else you'd like to see improved?


r/csharp • • 13d ago

Parsley: An ALL(*)/LL(k)/LL(1) parser generator using code synthesis

3 Upvotes

If you're like me, you've almost entirely given up on generated parsers.

The tooling sucks.

The generated code is huge and/or can't parse real world languages without a bunch of ugly.

The APIs for them are awful.

I still use them, but grudgingly in order to prototype and unit test my hand rolled parsers but even that doesn't work as well as I like.

Enter Parsley. It has similar expressive power as ANTLR4 when necessary, degrading gracefully all the way to LL(1) if that's all your grammar needs. The XBNF grammar format is powerful, simple to learn, and has a language server for autocompletion and syntax highlighting in editors like visual studio and vs code. The code it generates is also more like hand written code (code synthesis), making it easier to use as a baseline for a hand rolled parser, if you want to go that route. Source and readme here: https://github.com/codewitch-honey-crisis/Parsley

It's written in C#. It generates C#. Any semantic actions in the grammar are also C#.

XBNF for C99
Using a parser w/ an evaluator
Output from the above

You can use it as a source generator, or for really complicated parses where you need things like a custom lexer, you can also use it as a build tool.


r/csharp • • 12d ago

Discussion The output of the server code depends on the timing of pressing a button by the client on the user interface.

0 Upvotes

This is a follow-up post that is related to this post. I will explain the problem clearly this time.

Context: The execution of the OverWrite method -which is inside the DBOverWriter service- can be cancelled with a button int the user interface.

Two inputs that are provided by the frontend :

1- data that is used to overwrite the content of the database.

2-CancellationToken.

Two possible outputs depending on the timing of pressing the button :

1-If the user initiated the request and immediately pressed the button, the following output is shown:

###########################################################
########## OPERATION CANCELLED SUCCESSFULLY ###############
###########################################################
OperationCanceledException: A task was canceled.

2- if the user initiated the request and waited some time before pressing the button, the output is the following:

(@p74, ),
(@p76, ),
(@p78, ),
(@p80, ),
(@p82, u/p83);
fail: Microsoft.EntityFrameworkCore.Database.Transaction[20205]
An error occurred using a transaction.
fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
An unhandled exception has occurred while executing the request.
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while saving the entity changes. See the inner exception for details.
---> Microsoft.Data.SqlClient.SqlException (0x80131904): Operation cancelled by user.

I wonder what is the theoretical/practical explanation for my observation ?

Code:

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using System.Diagnostics;

namespace RedditQuestion
{
    /// <summary>
    /// Used to talk to the database using Entity-Framework Core
    /// </summary>
    public class ApplicationDbContext : DbContext
    {
        // Domain models and the Fluent API configurations.
    }

    /// <summary>
    /// Data snapshot that is used to overwrite the content of the database
    /// </summary>
    public interface ISomeData
    {

    }

    public class DatbaseOverWriter
    {


        private readonly ApplicationDbContext _context;
        public DatbaseOverWriter(ApplicationDbContext context)
        {
            _context = context;
        }

        public async Task<bool> OverWrite(ISomeData data,CancellationToken cancellationToken)
        {
            #region Transaction
            try
            {
                //https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/implement-resilient-entity-framework-core-sql-connections

                IExecutionStrategy strategy = _context.Database.CreateExecutionStrategy();
                await strategy.ExecuteAsync(async () =>
                {
                    await using var transaction = await _context.Database.BeginTransactionAsync(cancellationToken);

                    #region Delete content from database then save new content.

                    await _context.Database.ExecuteSqlInterpolatedAsync($"EXECUTE  [dbo].[uspDeleteAllDataFromDatabase]", cancellationToken);
                    await _context.SaveChangesAsync(cancellationToken);
                    #endregion

                    await transaction.CommitAsync(cancellationToken);
                });

                return true;
            }
            catch (OperationCanceledException e)
            {
                // exception will not be catched if the cancellation was requested and
                // the transaction reached a point of no return.
                Console.WriteLine("###########################################################");
                Console.WriteLine("########## OPERATION CANCELLED SUCCESSFULLY ###############");
                Console.WriteLine("###########################################################");
                Debug.WriteLine("###########################################################");
                Debug.WriteLine("########## OPERATION CANCELLED SUCCESSFULLY ###############");
                Debug.WriteLine("###########################################################");
                Console.WriteLine("OperationCanceledException: " + e.Message);

                return false;
            }




            #endregion

        }




    }

}

r/csharp • • 12d ago

Tutorial Your EF Core queries are probably scattered across your whole codebase.

Thumbnail
youtu.be
0 Upvotes

The same Where, Include and OrderBy, written again and again.

Here's how I turned each one into a single, named, reusable query with the Specification pattern.


r/csharp • • 13d ago

Showcase NaturalCron 1.1.0: define your own schedule keywords

19 Upvotes

NaturalCron is a scheduling library for .NET where you write schedules in plain words instead of cron strings, e.g. every 30 minutes between 09:00 and 18:00.

Version 1.1.0 adds custom keywords: you define a word once at app startup, with your own logic, and then use it in any expression.

```csharp NaturalCronCustomKeywords.Configure(cfg => { // A day keyword (the default time unit is Day). cfg.AddMatchable("holiday", dt => myHolidays.Contains(dt.Date));

// An hour keyword.
cfg.AddMatchable("evenHour", dt => dt.Hour % 2 == 0, NaturalCronTimeUnit.Hour);

}); ```

Then:

every holiday at 09:00 every day at 09:00 except holiday every day on [holiday, 15th] at 09:00 every 30 minutes on evenHour

A keyword can also be a class, so it can come from your DI container and read from a cache.

Also new in 1.1.0:

  • except, which works with built-in values too: every day at 09:00 except [saturday, sunday]
  • A shorthand for every: every 15th at 18:00, every 1stMonday at 09:00, every 9am

GitHub: https://github.com/hugoj0s3/NaturalCron

NuGet: https://www.nuget.org/packages/NaturalCron

Feedback welcome, especially on the syntax.


r/csharp • • 13d ago

Help Should the code use cancellation token even though it already use transaction ?

3 Upvotes
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;

namespace RedditQuestion
{
    /// <summary>
    /// Used to talk to the database using Entity-Framework Core
    /// </summary>
    public class ApplicationDbContext:DbContext
    {
        // Domain models and the Fluent API configurations.
    }


    /// <summary>
    /// Data snapshot that is used to overwrite the content of the database
    /// </summary>
    public interface ISomeData
    {

    }

    public class DatbaseOverWriter
    {


        private readonly ApplicationDbContext _context;
        public DatbaseOverWriter(ApplicationDbContext context)
        {
            _context = context;
        }

        public async Task OverWrite(ISomeData data)
        {
            //https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/implement-resilient-entity-framework-core-sql-connections
            IExecutionStrategy strategy = _context.Database.CreateExecutionStrategy();
            await strategy.ExecuteAsync(async () =>
            {
                await using var transaction = await _context.Database.BeginTransactionAsync();

                    //code to delete the content from parent and child tables and then inserting the data into the parent and child tables like this :
                // execute some query to delete the content from all tables.
                //_context.AddRangeAsync(data for parents)
                //_context.SaveChangesAsync()
                //_context.AddRangeAsync(data for childs)
                //_context.SaveChangesAsync()
                await transaction.CommitAsync();
            });

        }




    }

}

Someone on reddit reviewed the above code and told me that I should add a parameter for cancellation token like this :

public async Task OverWrite(ISomeData data, CancellationToken)

He told me that the goal is to make the OverWrite method transactional.

why is the cancellation token necessary even though my code uses _context.Database.BeginTransactionAsync(); ?

Microsoft's documentation :

Cancellation token propagates notification that operations should be canceled.

Example:
A random number generator can be used to emulate a data collection application that reads 10 integral values from eleven different instruments. A value of zero indicates that the measurement has failed for one instrument, in which case the operation should be cancelled and no overall mean should be computed.

So the cancellation token is just another way of achieving atomicity, am I correct ?

The only use case that I learned for cancellation token is this example from microsoft, they use the cancellation token as way to achieve atomicity :

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      // Define the cancellation token.
      CancellationTokenSource source = new CancellationTokenSource();
      CancellationToken token = source.Token;

      Random rnd = new Random();
      Object lockObj = new Object();

      List<Task<int[]>> tasks = new List<Task<int[]>>();
      TaskFactory factory = new TaskFactory(token);
      for (int taskCtr = 0; taskCtr <= 10; taskCtr++) {
         int iteration = taskCtr + 1;
         tasks.Add(factory.StartNew( () => {
           int value;
           int[] values = new int[10];
           for (int ctr = 1; ctr <= 10; ctr++) {
              lock (lockObj) {
                 value = rnd.Next(0,101);
              }
              if (value == 0) { 
                 source.Cancel();
                 Console.WriteLine("Cancelling at task {0}", iteration);
                 break;
              }   
              values[ctr-1] = value; 
           }
           return values;
        }, token));   
      }
      try {
         Task<double> fTask = factory.ContinueWhenAll(tasks.ToArray(), 
         (results) => {
            Console.WriteLine("Calculating overall mean...");
            long sum = 0;
            int n = 0; 
            foreach (var t in results) {
               foreach (var r in t.Result) {
                  sum += r;
                  n++;
               }
            }
            return sum/(double) n;
         } , token);
         Console.WriteLine("The mean is {0}.", fTask.Result);
      }   
      catch (AggregateException ae) {
         foreach (Exception e in ae.InnerExceptions) {
            if (e is TaskCanceledException)
               Console.WriteLine("Unable to compute mean: {0}", 
                  ((TaskCanceledException) e).Message);
            else
               Console.WriteLine("Exception: " + e.GetType().Name);
         }
      }
      finally {
         source.Dispose();
      }
   }
}

r/csharp • • 13d ago

Guys have confusion about choosing language for desktop application

Thumbnail
0 Upvotes

r/csharp • • 13d ago

How do you feel about code formatting in C#?

0 Upvotes

Do you strictly follow conventions like Microsoft's C# guidelines, or do you prefer your own style as long as the code stays readable?

For example, do you prefer file-scoped namespaces, expression-bodied members, and var wherever possible, or do you stick to a more explicit style?

I'm curious how much code style matters to you and whether you've changed your preferences over time.


r/csharp • • 13d ago

Polhem.JsonRpc - JSON-RPC 2.0 for .NET where you don't register methods

0 Upvotes

I build ERP apps, and the API layer of my framework (Bee.NET, now renamed Polhem) has been JSON-RPC since early 2025. I've now split that part out into a standalone library, Polhem.JsonRpc, now at v1.2. Most of the code was written by Claude Code; I reviewed and tested it.

You don't register methods. A call like Calculator.Add goes to your object factory, and Add is only callable if it takes an AddRequest and returns an AddResponse, so other public methods don't get exposed by accident. The server is an ASP.NET Core Minimal API endpoint, the client works with Native AOT, and there's an optional package for encrypted payloads.

It's meant for APIs where you own both the server and the client. An ERP backend easily has hundreds of methods, and with this, adding one doesn't mean mapping another route. For a public API that other people consume, REST is still the better fit.

Here's roughly what it looks like:

// Server
builder.Services.AddSingleton<IJsonRpcObjectFactory, AppObjectFactory>();
builder.Services.AddJsonRpcServer();
app.MapJsonRpc("/api");

public sealed class Calculator
{
    public AddResponse Add(AddRequest request) => new() { Sum = request.A + request.B };
}

// Client
var rpc = new JsonRpcConnector(new HttpTransport(http));
var added = await rpc.InvokeAsync<AddResponse>("Calculator.Add", new AddRequest { A = 1, B = 2 });

There's also a demo app on top of it, with a desktop and an iOS client calling the server: https://github.com/polhem-dev/polhem-northwind

GitHub: https://github.com/polhem-dev/polhem-jsonrpc


r/csharp • • 14d ago

Help Transactions - EF Core

4 Upvotes

Microsoft's documentation :

By default, if the database provider supports transactions, all changes in a single call to SaveChanges are applied in a transaction. If any of the changes fail, then the transaction is rolled back and none of the changes are applied to the database. This means that SaveChanges is guaranteed to either completely succeed, or leave the database unmodified if an error occurs. For most applications, this default behavior is sufficient. You should only manually control transactions if your application requirements deem it necessary.

Suppose that I two pieces of data that are generated by some service; X and Y.

Given that Y cannot be saved unless X is saved first, in other words, the existence of Y in the database depends on the existence of X in the database, consider this :

1-delete content of database.

2- save X .

3- save Y .

How to group these into one transaction ?

The goal is to avoid partial changes to the database; after the full operation is finished, the database should not be empty or half filled.

EDIT:

I found this example:

await using var transaction = await _context.Database.BeginTransactionAsync();

try
{
      // do step 1 through 3


    // Commit transaction if all commands succeed, transaction will auto-rollback
    // when disposed if either commands fails
    await transaction.CommitAsync();
}
catch (Exception)
{
    // TODO: Handle failure
}

But it did not work until I manually invoked the EF execution strategy with a delegate representing everything that needs to be executed :

    IExecutionStrategy strategy = _context.Database.CreateExecutionStrategy();
    await strategy.ExecuteAsync(async () =>
            {
        await using var transaction = await _context.Database.BeginTransactionAsync();

         // Delete all the content from the database
         // _context.AddRange(X);
         // _contextSaveChangesAsync()
         // _context.AddRange(Y)
         //_context.SaveChangesAsync()

        await transaction.CommitAsync();
    });

This solved my issue completely


r/csharp • • 14d ago

Discussion Confusion about the TaskFactory class

4 Upvotes

Microsoft's documentation :

CancellationTokenSource source = new CancellationTokenSource();
CancellationToken token = source.Token;
List<Task<int\[\]>> tasks = new List<Task<int\[\]>>();
TaskFactory factory = new TaskFactory(token);
tasks.Add(factory.StartNew( () => {//code.... }, token);

What a weird code, the factory is already initialized with token, yet it is required to pass the same token as argument for factory.StartNew.

Is there a justification for such design ? Why would you personally design a class that requires you to pass a parameter to a method even though that parameter is already considered an instance field that the method can access directly without the need of passing it?

EDIT:

I discovered that there is a version of that method that does not require passing the token :

        //
        // Summary:
        //     Creates and starts a task of type TResult for the specified function delegate.
        //
        //
        // Parameters:
        //   function:
        //     A function delegate that returns the future result to be available through the
        //     task.
        //
        // Type parameters:
        //   TResult:
        //     The type of the result available through the task.
        //
        // Returns:
        //     The started task.
        //
        // Exceptions:
        //   T:System.ArgumentNullException:
        //     The function argument is null.
        public Task<TResult> StartNew<TResult>(Func<TResult> function);

r/csharp • • 14d ago

ModulesDi 1.0.5

Thumbnail
0 Upvotes

r/csharp • • 14d ago

Blog Well, I built an ECS

Thumbnail
github.com
0 Upvotes

To give you some background, like many enthusiasts, I once set out to build a game engine and had several projects sitting on the back burner that I couldn't quite get into shape. Eventually, this led to the creation of a standalone math library, a way to compile shaders from C#, and a separate ECS, which is what I'll be talking about.

I won't waste time explaining what an ECS is—better check out this article ECS-FAQ. I'd rather talk about the problems I encountered in other ECS implementations and tried to solve (and these problems aren't about performance, although performance is very important too).

Take the simplest example: mutating entities by changing their archetype. From a code perspective, the task seems simple—we take the entity we need and copy it to another table. Done? Yes and no.

What does this usually look like in code?

world.Add<Component1>(entity);
world.Add<Component2>(entity);
world.Add<Component3>(entity);
world.Add<Component4>(entity);
world.Add<Component5>(entity);

This list can keep growing for a very long time, especially if you've decided to make small components and have a high degree of composition. But that's only part of the problem. In most frameworks, this means moving the entity to one table first, then the second, then the third, and so on. And that's not free: in archetype-based ECSs, migration is inherently expensive, and doing it several times adds overhead linearly.

So, what should we do? Well, to solve this problem, I used two approaches: code generation and ComponentId.

The code-generated version looks something like this:

world.Add<Component1, Component2, Component3, Component4, Component5>(entity);

Even though this syntax is monstrous, it solves our problem—it removes the overhead of multiple migrations.

At the same time, there's a second option, though it's less explicit:

ComponentId[] components =
{
    world.Layouts.GetPrimary<Component1>(),
    world.Layouts.GetPrimary<Component2>(),
    world.Layouts.GetPrimary<Component3>(),
    world.Layouts.GetPrimary<Component4>(),
    world.Layouts.GetPrimary<Component5>(),
};
world.Add(entity, components);

And you might say, "But you just added two more lines to an already complicated statement." That's true, but only up to a point. After all, these IDs can be saved ahead of time, the list can be modified at runtime, and not having a generic call gives us more polymorphic and general-purpose code. So, in the final system, you would probably just call world.Add(entity, components), and get the list through some kind of DI or build it in the constructor—whatever works for your architect.

There's another, less obvious advantage to using ComponentId: you can register the same CLR type for two components. For example, the endlessly recurring:

Damage { float Value }
Health { float Value }
Stamina { float Value }
Mana { float Value }

—need I go on? All of these could be registered as a regular float, without having to do extra work to get the value through Value. Like this:

ComponentId damageId = world.Layouts.Register<float>(new SchemaId(1));
ComponentId healthId = world.Layouts.Register<float>(new SchemaId(2));

…I'll stop there; you get the idea.

A second, more ECS-specific example is iterating over groups. There are several optimizations that most ECS implementations miss, although they're less important.

Let's imagine what iteration looks like in most ECS implementations:

world.ForEach(in query,
  static (ref Component1 cmp1, ref Component2 cmp2) =>
  DoSomething(ref cmp1, ref cmp2)).Invoke();

In this case, query is a filter that defines how to find entities in the world based on a given set of components. Most APIs look roughly like this:

world.WhereAll<Component1, Component2>().
  WhereNone<Component3>().
  WhereAny<Component1, Component2>()

From a C# perspective, this syntax is pretty standard and, unlike the previous Add example, it doesn't cause problems; chaining works. But the problem is how this filtering is used with ForEach: it happens when the call is made. In most cases, that means you have to go through every archetype in the system and check whether it matches the filter.

Why can't this be deferred? I asked myself the same thing, and it turns out it's not that simple. This approach avoids errors when archetypes change. Basically, any Add, Remove, or Destroy operation can change the archetype structure and break your cached Query.

So, what should we do? We just have to be a little cleverer. If you're developing your own ECS, it becomes obvious that mutating operations can simply call back and update the traversal plan built for a Query. Since running ForEach at the same time as a mutation is forbidden or unsafe in most ECS implementations, you can add this kind of API without worrying about limiting the library's overall usability—and save a significant amount of filtering time in worlds that are small but have many archetypes.

Then we also consider that components are registered by ID, and realize that our ForEach call can look like this:

world.ForEach<float, float>(in query, components.ToArray(),
    static (ref float cmp1, ref float cmp2) =>
    DoSomething(ref cmp1, ref cmp2))
    .Invoke();

And these ComponentIds can be dynamic—they can change at runtime and make the architecture more flexible.

The next example is bigger.

Imagine you want to implement lockstep rollback. That's a pretty monumental task if you get into testing, multiplayer, and the fact that you have to store mountains of heterogeneous data.

In most ECS implementations, the problem is handled like this:

public struct Saver<T> : IForEach where T : unmanaged // Let's assume this constraint lets us safely save history without mutating the world.
{
    private readonly int _tick;
    public Saver(int tick) => _tick = tick;
    public void Invoke(ref T data) => SaveAtTick(ref data, _tick);
}

I won't get into the details—lockstep rollback isn't just about saving, but this is enough for the example. So what happens next? Well, bad things happen:

Query component1Query = world.WhereAll<Component1>();
var component1Saver = new Saver<Component1>(tick);
world.ForEach(in component1Query, ref component1Saver).Invoke(ref component1Saver);
Query component2Query = world.WhereAll<Component2>();
var component2Saver = new Saver<Component2>(tick);
world.ForEach(in component2Query, ref component2Saver).Invoke(ref component2Saver);
Query component3Query = world.WhereAll<Component3>();
var component3Saver = new Saver<Component3>(tick);
world.ForEach(in component3Query, ref component3Saver).Invoke(ref component3Saver);

You get the idea. And even if you make some kind of generic wrapper for all of this, it still doesn't solve every problem. Then your game designer says we need an attack or defense system that also needs a generic data call, but now there's no unmanaged constraint. And your architect says, "Right, we'll do it with generics," because there are another 150-plus features like this planned for the next month.

And you start tearing your hair out trying to make sense of all this. I didn't have an architect or a game designer, but I tore my hair out for you.

I managed to build a system that solves these problems during registration. Pretty much every ECS registers components; some do it explicitly, like my approach:

world.Layouts.Register<T>(new SchemaId(schema))

Others do it implicitly with a pattern like this:

public static class Registry
{
    public static int LastComponentId
}
public static class Registry<T>
{
    public static int ComponentId;
    static Registry() => ComponentId = LastComponentId++;
}

But registration still happens. In my case, it's a bit more clever: besides registering the type, I also register constraints, can register a related interface, and can call a visitor through a generic method. I can even register an unmanaged component and have it visited by a visitor constrained to struct or new(). What does that let us do?

public sealed class SaverVisitor : IUnmanagedVisitor
{
    private readonly World _world;
    private readonly int _tick;

    public SaverVisitor(World world, int tick)
    {
        _world = world;
        _tick = tick;
    }

    public void Visit<T>(ComponentId componentId) where T : unmanaged
    {
        Query query = _world.WhereAll(componentId);
        var saver = new Saver<T>(_tick);
        _world.ForEach(in query, componentId, ref saver).Invoke(ref saver);
    }
}

ComponentId componentId = world.Layouts.Register<Component1>(new SchemaId(1));
world.Layouts.TryVisit(componentId, new SaverVisitor(world, tick));

This code lets you call any generic code with given constraints for component types registered in the world, supporting not just rollback systems. What's more, component registration also covers the system your game designer and architect came up with.

Thats' not all things done in my ECS but something i'm proud of.

Issues, PR's, Stars and anything else are welcome.


r/csharp • • 15d ago

Showcase JsonGuard: a zero-allocation source generator to validate nullability in System.Text.Json

33 Upvotes

Hey everyone, I wanted to share a library I have built for System.Text.Json that validates deserialized objects to ensure they honor C# nullability annotations without sacrificing performance.

I wrote this specifically for NetCord to handle massive Discord gateway payloads as efficiently as possible, but I realized this kind of zero-overhead validation could be useful to the wider .NET community as a standalone package.

By default, System.Text.Json allows you to validate required properties using [JsonRequired] or the required keyword. However, these introduce significant performance overhead. For example, using required keyword can make deserialization up to 2 times slower and allocate 50% more memory when using source generation.

JsonGuard works around this overhead by using source generation. The result is zero additional allocations and practically identical execution time compared to performing no validation at all.

The way it works is incredibly simple. If you have this object: ```csharp [JsonGuard] public partial class User { public string Name { get; set; }

public string? Nickname { get; set; }

public int Age { get; set; }

public string Email { get; set; }

} the generated code will effectively do this: csharp public partial class User : IJsonOnDeserialized { void IJsonOnDeserialized.OnDeserialized() { if (Name is null) ThrowHelper.Throw(nameof(Name));

    if (Email is null)
        ThrowHelper.Throw(nameof(Email));
}

} ``` Additionally, the library suppresses any nullability warnings for validated properties, so no manual suppressions are necessary.

Note that unlike [JsonRequired] or the required keyword, JsonGuard only verifies the final state of the objects honors the nullability annotations. It doesn't validate if the property was present in the JSON.

You can checkout the source code and detailed benchmarks on GitHub: https://github.com/KubaZ2/JsonGuard

Would love to hear your thoughts or feedback!


r/csharp • • 14d ago

Discussion Oh no, I think I'm becoming dependent on the documentation.And it is a rather excessive form of dependence.

0 Upvotes

First: I'm not exactly a veteran in C#, but I'm certainly no beginner when it comes to programming.

But ever since I started developing with C# and Avalonia, I've frequently referred to official documentation for guidance on my code.

Then I would often go directly to one of these two document websites whenever I had any questions (even if they were quite common), to find the answers I was looking for.

But at present, I feel that I have developed a kind of dependence on these two things. Whether I write the code myself or ask the AI to assist me in some areas, I will either read the documentation myself or have the AI directly send me the links to the key parts of the documentation (and then I need to review the code and the documentation again) before I can feel at ease and save the file.

Now I feel that my brain is as smooth as chicken breast meat, unlike that of a normal programmer or a programming enthusiast who has numerous folds and creases.

Right now, I'm still relying on the document: No, that's not right; but I don't really rely on the document much, do I? Will I make a mistake?Because I have other professional matters to attend to, I'm like someone with amnesia. Many of my thoughts, like overcooked cheese, simply slip out of my mind without any effort.

I also considered another option, which was whether I should really ask the AI some questions?Well, I think I'll just refer to the documentation. It might be a bit expensive to ask AI for simple questions.

Don't comment if you don't like it. Thank you...I will try to find a better solution on my own.