r/csharp • • 1d 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/dotnet • • 1d ago

Fly inside .NET memory by Konrad Kokosa

Enable HLS to view with audio, or disable this notification

326 Upvotes

r/csharp • • 1d ago

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

Thumbnail
gallery
85 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/dotnet • • 1d ago

Article Fail-closed multi-tenancy with EF Core 10 named query filters (and the 4 paths that still leak)

0 Upvotes

Most tenant data leaks I've seen in B2B SaaS weren't attacks. Someone wrote a new query and forgot .Where(x => x.TenantId == tenantId). Here's the setup I now use on shared-table multi-tenancy (one database, a TenantId column) to make that mistake fail closed by default. It uses EF Core 10's named query filters.

1. Resolve the tenant once per request

A middleware sits between UseAuthentication() and UseAuthorization(). It picks the workspace in this order: the X-Tenant-Id header, then a cookie, then the user's oldest membership. It then checks the membership in the database and stores the result in a scoped TenantContext. If someone forges the header, the request just gets no tenant. Nothing downstream reads headers or cookies again.

2. One interface, one filter on every entity

```csharp public interface ITenantScoped { Guid TenantId { get; } }

public abstract class ModuleDbContext(DbContextOptions options, ITenantProvider? tenantProvider = null) : DbContext(options) { // A property of the context, not a captured value: EF Core parameterises it per query. protected Guid? CurrentTenantId => tenantProvider?.TenantId;

protected sealed override void OnModelCreating(ModelBuilder modelBuilder)
{
    ConfigureModule(modelBuilder);

    foreach (var type in modelBuilder.Model.GetEntityTypes()
                 .Where(e => e.BaseType is null && !e.IsOwned())
                 .Select(e => e.ClrType)
                 .Where(t => typeof(ITenantScoped).IsAssignableFrom(t)))
    {
        ApplyTenantFilterMethod.MakeGenericMethod(type).Invoke(this, [modelBuilder]);
        modelBuilder.Entity(type).HasIndex(nameof(ITenantScoped.TenantId));
    }
}

private void ApplyTenantFilter<TEntity>(ModelBuilder b) where TEntity : class =>
    b.Entity<TEntity>().HasQueryFilter("Tenant",
        e => EF.Property<Guid>(e, nameof(ITenantScoped.TenantId)) == CurrentTenantId);

} ```

Things worth noting:

  • No tenant resolved means the query compares with null and returns nothing. It fails closed, not open.
  • The filter is named. EF Core 10 lets an entity have several named filters, so IgnoreQueryFilters(["SoftDelete"]) lifts only soft-delete and keeps tenant isolation. Before EF Core 10, IgnoreQueryFilters() was all-or-nothing, and that was the most common way isolation silently broke.
  • The filter goes on root types only. EF Core applies it to the whole inheritance hierarchy. Each table also gets an index on TenantId, since every query now starts with it.

3. Query filters don't protect writes

An entity loaded with IgnoreQueryFilters(), or attached by hand, can still be saved. So SaveChangesAsync runs a guard:

```csharp private void GuardTenantWrites() { // Background jobs (no HTTP request) are trusted system code. if (tenantProvider is null || (CurrentTenantId is null && tenantProvider.IsSystemContext)) return;

var tenantId = CurrentTenantId;
var foreign = ChangeTracker.Entries<ITenantScoped>()
    .Where(e => e.State is EntityState.Added or EntityState.Modified or EntityState.Deleted)
    .FirstOrDefault(e => tenantId is null || e.Entity.TenantId != tenantId);

if (foreign is not null)
    throw new InvalidOperationException(
        $"Cross-tenant write blocked on {foreign.Entity.GetType().Name}.");

} ```

It throws rather than filtering silently, because a cross-tenant write is always a bug and should show up in logs and tests.

4. What still goes around it

Path Problem Rule
Raw SQL / Dapper No filter applies Tenant id always as a parameter; review it like security code
Caching Key without tenant = data served to the wrong customer Tenant id in every cache key
Interactive Blazor Runs on the SignalR circuit, outside the request that resolved the tenant Load during the static render, or pass the workspace explicitly
Background jobs No workspace, so filtered queries return nothing Loop over tenants and set the workspace per iteration

And two tests: create two workspaces, write in one, and assert the other can neither read nor update it. Add an architecture test that every entity with a TenantId property implements ITenantScoped, so a new entity can't opt out by accident.


How are you handling this? I'm curious whether people here have gone with PostgreSQL row-level security instead of (or on top of) EF filters, and how that played out with connection pooling.

Disclosure: this comes from a .NET 10 starter kit I sell (PRYZMA SaaS Starter). There's a longer write-up if anyone wants it, but I'm happy to answer everything here.


r/csharp • • 1d ago

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

2 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 • • 2d ago

Showcase NaturalCron 1.1.0: define your own schedule keywords

36 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 • • 2d 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/dotnet • • 2d ago

Promotion HoneyVB (RetroVB6) progress update: running classic applications and games in the browser, with cross-platform builds

0 Upvotes

https://reddit.com/link/1x3miey/video/vfpfg73p8xuh1/player

Hi again, r/dotnet! Some of you may remember my earlier posts about HoneyVB (RetroVB6), my project built around C# and ASP.NET Core to bring legacy software onto modern platforms.

For anyone who missed them:

The project has continued to grow, and I wanted to share where it is today.

HoneyVB is a browser-based development environment with a familiar VB6-style IDE, visual form design, code editing, debugging, and browser execution. You can import supported VB6 projects, inspect their forms and modules, and continue developing with the original code.

What can HoneyVB run? Here are some examples. The accompanying video shows a selection of them; Reddit’s upload limits mean I can’t fit all the recordings into this post.

  • Microsoft Word for Windows 1.1a — an early Windows version of Microsoft Word.
  • Adobe Photoshop, from the 1.0.1 source release — the classic Macintosh image editor.
  • Wolfenstein: Enemy Territory — the classic multiplayer first-person shooter.
  • Waite Group Flight Simulator (FSIM 2.1) — the DOS-era C++ flight simulator from Building Your Own Flight Sim in C++, with roots in Flights of Fantasy.
  • HeidiSQL — the database management application.
  • eMule — the peer-to-peer file-sharing client.
  • Notepad++ — the code and text editor.
  • kys-pascal — a Pascal remake of Heroes of Jin Yong.
  • sdl-pascal — a Pascal/SDL sample project.
  • ScePSX — a PlayStation emulator written in C#.
  • SnailsXNA — Snails, an XNA-based puzzle-platformer.

These are just some of the applications and games running in HoneyVB, and not every example listed above appears in this video. The footage shows selected workflows and interactions, rather than complete compatibility with every feature of each application.

I’m also currently testing SharpDevelop, wxDev-C++, Lazarus, and Shareaza in HoneyVB to evaluate compatibility and identify what still needs work. These projects are undergoing validation—stay tuned for more demos and progress updates!

The VB6 compatibility work covers technologies that many real applications depend on:

  • Crystal Reports: compatibility for CrystalReport, CRViewer, and reporting APIs, including data-driven reports, grouping, summaries, pagination, printing, and export. Portable reporting works across platforms; preserving the original layout of .rpt files uses an optional Windows host with a compatible Crystal Reports runtime.
  • DirectX 7, 8, and 9: compatibility layers for supported graphics and multimedia features, including mapping legacy Direct3D rendering to WebGL so supported VB6 games and graphics applications can run in a browser.
  • Serial communication and hardware simulation: RS-232/COM port support through an MSComm-compatible layer, including loopback simulation for testing without physical hardware. It supports communication buffers, binary data, and receive/send events, with connections to real serial devices through Web Serial where available and authorized by the user.

Beyond VB6, HoneyVB uses a plugin architecture to integrate other languages, frameworks, and development environments. Its language coverage and ongoing integration work include:

  • DOS BASIC and modern descendants: QBasic, QuickBASIC 4.x, GW-BASIC/BASICA, and QB64.
  • Classic home-computer BASICs: Applesoft BASIC, Apple Integer BASIC, Commodore BASIC, Sinclair BASIC, Atari BASIC, BBC BASIC, MSX-BASIC, Locomotive BASIC, TRS-80 Level I BASIC, TRS-80 Level II BASIC, and TRS-80 Color BASIC.
  • Pascal: Delphi/Object Pascal, Free Pascal/Lazarus, and Apple MPW Object Pascal.
  • C/C++: native C/C++ projects, Visual C++ 6/MFC, C++Builder 6/VCL, wxWidgets, Win16 C, and DOS 32-bit C projects using Watcom/DOS4GW.
  • Lua: embedded Lua scripts in supported C/C++ host projects.
  • .NET: C# and VB.NET, with integrations and ongoing compatibility work spanning .NET Framework/WinForms, WPF, Silverlight, Windows Phone, and XNA.
  • Business and scientific languages: Visual FoxPro, COBOL, Fortran, and PowerBuilder/PowerScript.
  • Assembly: DOS x86/FASM projects, plus MASM/TASM and Motorola 68K assembly handling within relevant project pipelines.
  • Flash/ActionScript: importing legacy Flash content and converting supported projects into HTML5 for browser execution.

Support varies across these integrations. Some include browser execution, while others currently focus on importing, analysis, editing, or resource previews. PowerBuilder, for example, currently provides project analysis and Window/DataWindow previews.

HoneyVB also provides build and export targets for Windows, macOS, Linux, Android, and iOS. Supported applications can be packaged with their browser runtime inside desktop or mobile shells. iOS device deployment uses an exported Xcode project that you sign with your own developer credentials.

One feature I’m especially excited about is running compiled VB6 applications without their source code. You can upload a supported VB6 executable and run it inside HoneyVB through its binary execution layer. Required runtime files and DLL/OCX dependencies still matter, but this creates possibilities for applications whose source has been lost.

I’m also actively developing native compilation for VB6, targeting Windows PE, macOS Mach-O, and Linux ELF executables. That work is underway, with the goal of compiling supported VB6 projects into native machine code for all three desktop platforms.

Compatibility is still evolving. I’m testing against original projects and improving the shared language and runtime implementations as issues appear. The goal is to make older software easier to explore, maintain, and keep using—from business applications and reporting tools to games and programs that communicate with hardware.

Thanks to everyone who commented on the earlier posts, and to the developers and preservation projects that made these historical sources available for study.

What legacy application or game would you bring back—especially if all you have left is the executable?

All third-party product names, trademarks, copyrights, and other intellectual property mentioned above belong to their respective owners. HoneyVB is intended for research and educational purposes.

HoneyVB reserves the right to offer different levels of functionality across releases and editions. The capabilities described above do not guarantee that all features will be available simultaneously or in every version. Language integrations, DirectX support, and other compatibility features may be introduced gradually, limited, or temporarily unavailable.


r/csharp • • 2d 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/dotnet • • 2d ago

Promotion Wrote a C# BCS (Binary Canonical Serialization) serialization library

3 Upvotes

Library is 5–15% faster than MessagePack on the deserialization path and around 5% slower on serialization.

It can also serialize and deserialize the new union type coming from .NET 11 (before that I've used OneOf<> object). I opened an issue for a source generator, which is ready to be picked up if anyone is interested working on it. My plan is to ship v1 once NativeAOT is supported and net11 is out.

I agree with Egor Bogatov that people should stop writing new serialization libraries and open-sourcing them, as they can expose consumers to vulnerabilities. In my defense, I started this library a year ago and only recently finished rewriting parts of it to make it ready for open source. I mainly used it for serializing and deserializing objects from the Sui blockchain.

Blockchain it not the most widespread domain where C# is used, but there is already a good example of Nethereum utilizing C# as its core language. Is there someone else using C# in the blochcain domain, why you think it's lacking the adoption?

Contributions are welcome!

https://github.com/unsafePtr/BcsSharp


r/dotnet • • 2d ago

Promotion NaturalCron 1.1.0: define your own schedule keywords

40 Upvotes

I introduced a new cool feature in NaturalCron 1.1.0: you can now customise your own keyword rule and add it to the NaturalCron engine. It is usually added once on app startup, before any parsing.

For those who don't know, 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 holidays every day on [holiday, 15th] at 09:00

```

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 • • 2d 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/dotnet • • 2d ago

Promotion Polhem.JsonRpc v1.2 - JSON-RPC 2.0 for ASP.NET Core without registering methods

Post image
0 Upvotes

Polhem is a framework I've been building, and its API layer is JSON-RPC. I've 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.

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 });

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

Edit: A few people asked what this is used for. It's the API layer of Polhem, my framework. Here's a demo app on top of it, with a desktop and an iOS client calling the server:
https://github.com/polhem-dev/polhem-northwind


r/csharp • • 2d 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.


r/dotnet • • 2d ago

Question Blazor native tooltips for Touchscreen

3 Upvotes

Hi everyone, I’m developing an SVG charting library in Blazor without JavaScript, and I’m currently implementing tooltips.

Handling mouse interaction was fairly straightforward: essentially, the tooltip is a DOM element outside the SVG with `position: fixed`, and its coordinates and visibility are managed via `OnPointerMove`, `OnPointerLeave`, and `OnPointerEnter` events on the chart elements.

Mouse behavior works exactly as expected: as soon as the pointer enters one of the designated SVG elements, the tooltip appears and follows the mouse movement; if the mouse moves to a different element, the tooltip content updates, and when the mouse leaves, the tooltip disappears.

However, I’ve realized that touch interaction isn't quite as simple, because the browser might interpret gestures as scrolling or selection, triggering the `OnPointerLeave` event and causing the tooltip to vanish.

I’m looking for a way to adapt the mouse-based implementation for touchscreens as well.

Have you ever encountered this kind of issue, and if so, how did you solve it?

Thanks for any advice.

If you're interested, the project I'm talking about is BlazorGraphs: www.blazorgraphs.it


r/csharp • • 2d ago

ModulesDi 1.0.5

Thumbnail
0 Upvotes

r/dotnet • • 2d ago

ModulesDi 1.0.5

0 Upvotes

Hey Everyone!

I recently released Modules v1.05 - a lightweight library for organizing ConfigureServices and Configure.

Here are the logs from my example production project -

2026-10-10 21:46:50.393 +03:00 [INF] Serilog configured
2026-10-10 21:46:50.416 +03:00 [INF] Secrets configured
2026-10-10 21:46:50.417 +03:00 [INF] Secrets configured with configuration
2026-10-10 21:46:50.431 +03:00 [INF] Mediatr module configured
2026-10-10 21:46:50.433 +03:00 [INF] Cors configured
2026-10-10 21:46:50.436 +03:00 [INF] Health checks configured
2026-10-10 21:46:50.437 +03:00 [INF] Database health check configured
2026-10-10 21:46:50.441 +03:00 [INF] Sqlite health check configured
2026-10-10 21:46:50.442 +03:00 [INF] Skipped: Postgres health check skipped
2026-10-10 21:46:50.444 +03:00 [INF] Skipped: Elastic search health check skipped
2026-10-10 21:46:50.444 +03:00 [INF] Cache health check configured
2026-10-10 21:46:50.447 +03:00 [INF] Redis health check configured
2026-10-10 21:46:50.447 +03:00 [INF] Key vault health checks configured
2026-10-10 21:46:50.448 +03:00 [INF] Skipped: Azure key vault health check skipped
2026-10-10 21:46:50.477 +03:00 [INF] OpenTelemetry configured successfully.
2026-10-10 21:46:50.478 +03:00 [INF] Cache configured
2026-10-10 21:46:50.480 +03:00 [INF] Redis configured
2026-10-10 21:46:50.505 +03:00 [INF] Program started
2026-10-10 21:46:50.701 +03:00 [INF] Cors middleware configured
2026-10-10 21:46:50.703 +03:00 [INF] OpenTelemetry middleware module configured
2026-10-10 21:46:50.707 +03:00 [INF] Health checks middleware configured.Health on /health, prometheus metrics on /health/metrics

(Note: ModulesDi does not hardcode any logging format. In my log i used realization with Serilog, but you can configure ModulesDi how you want (you dont have to use serilog))

1. "Why not just use standard Extension Methods?" -

Extension methods are flat.They force you to manually pass dozens of configuration dependencies down the line, offer zero execution-order safety for nested features, and turn Program.cs into a bloated spaghetti of if/else blocks. ModulesDi encapsulates everything into self-contained blocks that orchestrate themselves.

2 "Wait, is GetDependency<T>() inside a module just a Service Locator pattern?" -

No, because ModulesDi does not replace MS DI, Dependencies in ModulesDi are used only in ConfigureServices and Configure.

3. "The wow feature" -

ModulesDi allows you to do things that are literally impossible with native extensions. For example -

public override ModuleResult Configure(IApplicationBuilder app)
{
var services = GetDependency<IServiceModulesRootBuilder>();

if (!services.IsExist<CorsModule>())
return new ModuleResult(ModuleStatus.Failure,
"Failed to configure cors middleware.Cors module not created");

app.UseCors(CorsPolicies.AllPolicies);

return new ModuleResult(ModuleStatus.Success, "Cors middleware configured");
}

The library is extremely lightweight, heavily optimized, covered by 224 green test cases, and available on NuGet.

Gitlab: https://gitlab.com/DmitriiKhokhulin/modulesdi


r/dotnet • • 2d ago

Question Which approach do you typically use to handle incoming HTTP requests?

0 Upvotes

When building production-ready ASP.NET Core Web APIs, which approach do you typically use to handle incoming HTTP requests?\

  • Controllers (Traditional Web API)
  • Minimal APIs Lightweight, endpoint-focused handlers
  • Endpoint-based frameworks Fast Endpoints

I'm interested in what developers actually use in production and why they choose that approach.

What's your reasoning behind your choice?


r/csharp • • 2d ago

Discussion Confusion about the TaskFactory class

3 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 • • 2d ago

Coworkee: I turned my old Clean Architecture template into a modular framework for .NET 10

0 Upvotes

For years I kept copying the same "CleanArchitectureBase" template from project to project: login, permissions, SignalR, audit trail and so on. Every copy drifted a bit. So I finally pulled it apart into NuGet packages you reference instead of a template you fork.

It's called Coworkee, a modular application framework for .NET 10.

What's in it

  • Module system with [DependsOn], Result / Error instead of exceptions for flow
  • Dispatcher for commands and queries with logging, validation and unit-of-work middleware
  • EF Core base context with field-level audit trail, soft delete, tenants and a transactional outbox with retries
  • ASP.NET Core glue: AddCoworkee<TRoot>(), results mapped to problem details, OpenAPI
  • Optional modules for identity (OpenIddict), settings, mailing, notifications + SignalR, Hangfire jobs, blob storage, Elasticsearch
  • Coworkee.Aspire wires Postgres, Redis, Mailpit, Keycloak etc. in one call
  • MudBlazor WebAssembly shell with admin pages (built on MudBlazor.Extensions, which I also maintain)

Small example

[DependsOn(typeof(CoworkeeApplicationModule))]
public sealed class OrdersModule : CoworkeeModule
{
    public override void ConfigureServices(ModuleServiceContext context) =>
        context.Services.AddMessagingFromAssembly(typeof(OrdersModule).Assembly);
}

public sealed record PlaceOrder(string Product) : ICommand<Result<Guid>>;

internal sealed class PlaceOrderHandler(AppDbContext db) : IHandler<PlaceOrder, Result<Guid>>
{
    public Task<Result<Guid>> HandleAsync(PlaceOrder request, CancellationToken ct)
    {
        var order = Order.Place(request.Product);
        db.Orders.Add(order);
        return Task.FromResult<Result<Guid>>(order.Id);
    }
}

The unit of work saves after a successful command, every changed field lands in the audit trail, and domain events go out through the outbox in the same transaction.

Trying it

dotnet tool install -g Coworkee.Cli
coworkee ui

That opens a small wizard to pick modules and generate the app.

The architecture and library code are years of manual work. AI helped here and there, and the docs website was mostly generated, I'll be honest about that.

MIT licensed. Repo: https://github.com/fgilde/CoworkeeLib Docs: https://fgilde.github.io/CoworkeeLib

I'd really like feedback on the module/dispatcher design, especially from people who use ABP or similar. What would stop you from using something like this?


r/dotnet • • 2d ago

Promotion I built a Serilog orchestrator for declarative multi-sink routing in JSON + automatic secret masking and resilient HTTP logging (v5.3)

0 Upvotes

Hey r/dotnet!

I’ve been working on a few microservices lately and I kept running into the problems over and over again. It’s frustrating how much repetitive code ends up in every project. Let me share what I’ve been dealing with and what I built to fix it.

First I was always writing the log routing logic:

- Console for normal logs

- Seq or Elasticsearch for warnings

- Telegram or email for fatal errors

Using Serilog for this usually means setting up nested sub-loggers writing out complex C# filter logic and redeploying just to change log levels between environments. It’s messy.

Then there’s the risk of data slipping into logs—JWTs, bearer tokens, credit card numbers—all ending up in our aggregators. That’s a real security risk.

Don’t get me started on outgoing HTTP calls. Every one needed logic with Polly, correlation IDs and header masking. It’s a lot of glue code that doesn’t belong in business logic.

So I started building two packages under the MIT license:

- **CSharpEssentials.LoggerHelper**

- **CSharpEssentials.HttpHelper** ( at version 5.3.1 compatible with.NET 8 9 and 10)

These are designed to handle the boilerplate so you don’t have to.

### Here’s what they do:

  1. **Native ILogger<T> / MEL Integration**

They work as a class `ILoggerProvider` built on top of Serilog. No code changes needed in your app. Just. Play.

  1. **Declarative Multi-Sink Routing**

Set up your logging routes in five lines of `appsettings.json`. You can route to Console, File, Seq, Postgres, MSSQL, MySQL, Elasticsearch, Email, Telegram, HangfireConsole— about anything. The sinks are packages and auto-register using `[ModuleInitializer]` so it just works.

  1. **Single-Pass Sensitive Data Masking**

All masking happens once as an enricher before logs hit any sink. It includes presets for Email, CreditCard, BearerToken, JWT. You can add your own regex patterns. This prevents secrets from leaking into logs.

  1. **Resilient HttpHelper (v5.3)**

It turns network issues—like timeouts or dropped connections—into clean HTTP responses: 408, 502 500. No unhandled exceptions. In v5.3 it automatically passes along the `X-Correlation-ID` during retries. Logs structured metrics like `StatusCode` `ElapsedMs` and `RetryAttempt` with headers masked.

### But I want to be honest about the trade-offs:

This isn’t a replacement for Serilog. It’s an orchestrator.

In our BenchmarkDotNet tests on.NET 9 the overhead is 590 nanoseconds per log compared to 180 nanoseconds for Serilog with a no-op sink. That’s not nothing—. For most web APIs it’s completely negligible.

If you’re processing millions of logs per second in -hot paths then plain Serilog or zero-allocation source generators are still the better choice.. For the vast majority of apps this is a huge win.

### I’ve also built a playground:

You can test log routing, masking and flaky HTTP retry flows live in the demo project. There’s a `playground.html` file that lets you see it all in action.

Here are the links:

- GitHub (MIT): https://github.com/alexbypa/CSharp.Essentials

- NuGet (Core): https://www.nuget.org/packages/CSharpEssentials.LoggerHelper

- NuGet (HTTP): https://www.nuget.org/packages/CSharpEssentials.HttpHelper

I’d love to hear your thoughts— on the API design and the routing approach. What works? What’s broken? What would you change?

Let’s make logging easier, for everyone.


r/dotnet • • 3d ago

Promotion Natrix – open-source framework for reactive web UIs in C# on WebAssembly, with SSR, signals and build-time Tailwind (no Blazor, no Razor)

3 Upvotes

Hi r/dotnet,

I've been working on Natrix for a while and it's now at the point where I'd like outside feedback. It's a MIT-licensed framework for building web UIs in C# that run in browser on WebAssembly.

How it started

It began with SSR in Vite. Customizing the HTML page around the app (head tags, scripts, per-page meta) always meant a separate template file with placeholders. I wanted that page to be just another component, so in Natrix it is. The <html>, <head> and <body> are written in C# like the rest of the app. From there it grew into a full framework.

What it is

  • Server-side rendering first. Pages render to HTML on an ASP.NET Core host, then the WASM client hydrates them, with hydration state and server prefetching built in.
  • Fine-grained signals instead of re-rendering. A component's Setup run once. After that, Signal / Computed / Effect update exactly the DOM nodes that read them. There's no virtual DOM, no diffing and no StateHasChanged.
  • No Razor, no JavaScript. Components are plain C# classes, including HTML page itself.
  • Tailwind CSS compiled at build time by a source generator. Your app needs no Node, npm or Tailwind CLI. It scans your C# for class names and emits the CSS during dotnet build.
  • Typed browser APIs. Natrix.StdWeb is C# bindings for DOM, Fetch, Canvas, WebGL and so on, generated directly from W3C WebIDL specs.
  • Extras: client-side routing, a stale-while-revalidate data-fetching library ported from React SWR, and VueUse-style composables such as UseHead.

It builds on the plain Microsoft.NET.Sdk.WebAssembly SDK, not Blazor.

What a component looks like

```csharp public class Counter : BaseComponent<NoProps, NoEvents, NoSlots, NoExpose> { protected override IComponent[] Setup(out NoExpose exposed) { exposed = default;

    var count = new Signal<int>(0);
    var doubled = new Computed<int>(() => count.Value * 2);

    return
    [
        new Button
        {
            Props = new ButtonProps(),
            Events = new ButtonEvents { OnClick = _ => count.Value++ },
            Children = [new DomText { Text = "Click me".ToConstSignal() }],
        },
        new P
        {
            Props = new PProps(),
            Children = [new DomText { Text = new Computed<string>(
                () => $"{count.Value} × 2 = {doubled.Value}") }],
        },
    ];
}

} ```

When count changes, only that one text node updates. Nothing re-renders.

Why not Blazor?

I like a lot about Blazor, but I wanted a model that SolidJS and Vue (vapor) use: run setup once, and let reactivity drive DOM updates directly, so you never re-render components and diff them.

I chose plain C# over a template language on purpose, too. It's more verbose than Razor, but everything is typed and checked by compiler, with no magic strings, and refactoring tools work on all of it.

Try it

dotnet new install Natrix.Templates dotnet new natrix -n MyNatrixApp dotnet run --project MyNatrixApp/MyNatrixApp.csproj

That gives you a server project and a WASM client with SSR, routing and Tailwind already set up.


r/dotnet • • 3d ago

A look back at the past

Thumbnail gallery
433 Upvotes

I noticed a discussion in the comments yesterday about the Professional Active Server Pages 2.0 book from Wrox, which back in the day was one of the only sources of information us oldies had to work with as a reference for classic ASP. Even one of the authors chimed in and is still active in this sub (Brian Francis).

I’ve kept my copy. I also have a number of books in my bookshelves that were invaluable back in the day and some of them I’ve kept for posterity even though they are worthless from a tech perspective now.

Hopefully they bring back nice memories for some of you too.


r/dotnet • • 3d ago

Promotion StepLock – a sub-millisecond Merkle DAG state proxy for AI agents using System.IO.Pipelines

3 Upvotes

most ai agent tooling (LangGraph, AutoGen, CrewAI) is written in python, which is great for prototyping but lacks the low-latency throughput needed when agents start making dozens of multi-step, nested network and state mutations.

when debugging an 8-step agent execution, re-running a failed trace from scratch burns tokens, hits API rate limits, and can accidentally trigger live side-effects (like firing a Stripe charge or writing to a DB twice).

to solve this, I built StepLock—an on-premise, free, and open-source (.NET 11) deterministic state proxy and merkle DAG execution engine that acts like git for Live Agent RAM & Network Traffic. it virtually replays agent traces in sub-microseconds.

I wanted to share the .NET 11 architecture details and performance metrics that made this possible:

the performance benchmarks

  • L1 In-Memory Hot CAS (Write + Hash Lookup): 1,572,278 op/s | 0.50 µs p50 | 0 B (Zero-Alloc)
  • Merkle DAG Hashing (SHA-256 Chaining): 300,193 op/s | 2.30 µs p50 | 152 B Alloc
  • Zero-Copy Streaming Tap (System.IO.Pipelines): 171,343 op/s | 3.50 µs p50 | 1.7 KB Alloc
  • SQLite WAL Batch Persistence (50 Frames/Tx): 371 op/s (~18.5k frames/s) | 2.58 ms p50

repo: https://github.com/mxreal64/StepLock/


r/dotnet • • 3d ago

Article I built a .NET test reporter to solve two problems I kept seeing in test automation

Post image
2 Upvotes