[HN Gopher] Comptime - C# meta-programming with compile-time cod...
       ___________________________________________________________________
        
       Comptime - C# meta-programming with compile-time code generation
       and evaluation
        
       Author : bj-rn
       Score  : 133 points
       Date   : 2025-12-20 20:49 UTC (5 days ago)
        
 (HTM) web link (github.com)
 (TXT) w3m dump (github.com)
        
       | mfro wrote:
       | This seems like the kind of feature that should be built into
       | MSBuild.
        
         | eterm wrote:
         | It's a lot less ergonomic but there are source generators in C#
         | :
         | 
         | https://devblogs.microsoft.com/dotnet/introducing-c-source-g...
         | 
         | That said, for more complex results, you'd typically load a
         | serialization on start.
         | 
         | I can see the value in this tool, but there must be a fairly
         | limited niche which is too expensive to just have as static and
         | run on start-up and cache, but not so large you'd prefer to
         | just serialize, store and load.
         | 
         | It also needs to be something that is dynamic at compile time
         | but not at runtime.
         | 
         | So it's very niche, but it's an interesting take on the
         | concept, and it looks easier to use than the default source
         | generators.
        
           | piskov wrote:
           | Also t4 templates before that for at least a decade
        
             | pjmlp wrote:
             | And much more developer friendly.
        
           | richardw wrote:
           | Not terribly niche. All config that isn't environment-
           | specific and is used in inner loops or at startup. It's even
           | got a test for serialised values so can be used to speed your
           | case up:
           | 
           | https://github.com/sebastienros/comptime/blob/main/test/Comp.
           | ..
           | 
           | But you need to be sure you won't want to change without
           | compiling.
        
             | eterm wrote:
             | Well it also needs to be something that you need to
             | generate/calculate, otherwise you would just write by hand
             | the code that comptime outputs.
        
           | mexicocitinluez wrote:
           | I use source generators pretty extensively but don't really
           | understand how this is different (or what it's solving that
           | source generators can't).
           | 
           | I'm pretty new in the source generation area and only do
           | enterprise dev, so I'm sure I'm missing something or just
           | don't have the use cases.
        
           | Const-me wrote:
           | > there are source generators
           | 
           | Last time I tried them discovered source generators in the
           | current .NET 10 SDK are broken beyond repair, because
           | Microsoft does not support dependencies between source
           | generators.
           | 
           | Want to auto-generate COM proxies or similar? Impossible
           | because library import and export are implemented with
           | another source generators. Want to generate something JSON
           | serializable? Impossible because in modern .NET JSON
           | serializer is implemented with another source generator.
           | Generate regular expressions? Another SDK provided source
           | generator, as long as you want good runtime performance.
        
             | tubs wrote:
             | You can source generate JSON serdes from your own source
             | generator but you do need to generate the jsontypeinfo
             | metadata yourself.
        
         | andix wrote:
         | Please don't. Nobody wants even more complexity in MSBuild ;)
        
         | ygra wrote:
         | The use cases are different. While MSBuild tasks run during
         | build (and partially when loading a project), typically the IDE
         | is oblivious what happens there. The source generator runs
         | directly inside the compiler infrastructure and thus you didn't
         | get error highlights for code that would otherwise be only
         | generated during build but not as you type. This makes it much
         | more friendly than pure build-time generation of code.
        
       | mgaunard wrote:
       | So it's like C++ consteval?
        
       | CharlieDigital wrote:
       | C# meta programming game is strong. Source generators are
       | :chefs_kiss:
        
         | daeken wrote:
         | I love (and heavily use) source generators, but the development
         | experience is godawful. Working with the raw Roslyn types is
         | painful at best and this is compounded by them having to be
         | written against .NET Standard, severely limiting the use of
         | newer .NET functionality.
         | 
         | Eventually I want to write a good baseline library to use for
         | my source generators -- simplifying finding definitions with
         | attributes, mapping types to System.Type, adding some basic
         | pattern matching for structures -- but haven't found a way to
         | do it that's general enough while being very useful.
        
           | pjmlp wrote:
           | Yeah, it is kind of sad that it was the community that had to
           | step up for some T4 like experience instead of string
           | concatenation.
           | 
           | That isn't as cool as Aspire and AI features.
        
           | mexicocitinluez wrote:
           | I feel the exact same way. They can be insanely powerful, but
           | the syntax is insanely off-putting.
           | 
           | And the documentation is still pretty sparse.
           | 
           | > Eventually I want to write a good baseline library to use
           | for my source generators -- simplifying finding definitions
           | with attributes, mapping types to System.Type, adding some
           | basic pattern matching for structures -- but haven't found a
           | way to do it that's general enough while being very useful.
           | 
           | I'd use it in a heartbeat. The new
           | ForAttributeWithMetadataName function has been a big help,
           | but everything else feels like a totally different language
           | to me.
        
           | zigzag312 wrote:
           | I agree, .NET Standard limitation unnecessarily complicates
           | development experience. I think it's because some tools
           | (Visual Studio) is still use legacy .NET Framework. I don't
           | understand why they didn't integrate them via out of process
           | architecture into these tools, since source generators didn't
           | exist in the legacy framework anyway.
           | 
           | I sometimes generate code from plain CLI projects (avoiding
           | source generators altogether), as whole debugging and DX is
           | so much better.
        
             | tryfinally wrote:
             | You add PolySharp to your source generator project to get
             | back some of the modern C# features.
             | https://github.com/Sergio0694/PolySharp
        
             | daeken wrote:
             | Yeah, I went with that approach for most of the code
             | generation in the emulator I'm currently working on. Source
             | generators handle a few core things, but more advanced
             | compilation tasks went to just ahead of time generation;
             | couldn't get my parser combinator library to play nicely
             | with .NET Standard, so that was just a dead-end.
        
             | octopoc wrote:
             | .NET standard isn't the biggest issue with making source
             | generators. You can't add dependencies to your project,
             | which is an absolutely huge oversight IMO.
        
         | torginus wrote:
         | I used to think so too, but I do have to complain about
         | Microsoft doing the Microsoft thing - they have a brilliant
         | idea (and execution), but they bury it in so much boilerplate
         | that 99% of people who would use it are put off by it.
         | 
         | The amount of stuff you have to wade through here compared to
         | something like comptime in Zig (Roslyn API, setting up the
         | project, having VS recognize it and inject it in the compiler,
         | debugging etc) makes usage of these an absolute pain.
        
       | smcnc wrote:
       | I think Zig really shines here:
       | https://ziglang.org/documentation/master/#comptime
        
         | Zambyte wrote:
         | A key difference is that in this C# package, `[Comptime]` is an
         | attribute (annotation? not sure on the C# term) applied to
         | methods. In Zig, the `comptime` keyword can be applied to
         | pretty much any expression. In the C# package, if you want to
         | do factorial at runtime and at compile time, (I think, from
         | reading the README) you need to define the same function twice,
         | one with `[Comptime]` and once without. Contrast this to Zig,
         | where if you have a regular runtime factorial function, you can
         | just execute it at compile time like:                   const x
         | = comptime factorial(n);
         | 
         | Another limitation of the C# package is it only works with
         | primitive types and collections. Zig comptime works on any
         | arbitrary types.
        
           | hahn-kev wrote:
           | You don't. The way it works is that it intercepts the call
           | site when the input args are constant. If they're not then it
           | won't be replaced and it will call the original method. C#
           | source generators can't replace method definitions, however a
           | call site can be changed to another method via source
           | generators.
        
             | NuclearPM wrote:
             | You don't what?
        
               | ygra wrote:
               | You don't have to write the method twice. Source
               | generators can only add new code, they cannot take away a
               | method you declared, so it will still be there at runtime
               | when called with non-constant arguments.
        
           | estimator7292 wrote:
           | Decorations in [square brackets] are Attributes in C#
        
         | Tiberium wrote:
         | There's also Nim, it shines even more in compile time and has
         | proper compile time macros :)
        
         | pjmlp wrote:
         | D was already doing it in 2010 thereabouts, an then there is
         | the whole reader macros in Lisp and Scheme.
        
           | jibal wrote:
           | And Nim, which has multiple levels of metaprogramming.
        
         | pyrolistical wrote:
         | I'm annoyed they called it comptime when it isn't the same as
         | Zig's more powerful comptime.
         | 
         | You can think of zig's comptime as partial evaluation. Zig
         | doesnt have a runtime type reflection system, but with comptime
         | it makes it feel like you do.
        
       | jauntywundrkind wrote:
       | Makes me think of Boo language; Boo was so good at
       | metaprogramming and multi-phasr programming! A very fine .NET
       | language that was so far ahead of the curve, with having the
       | tools of that language be usable at runtime.
       | 
       | Alas many of the docs are offline now. But it had great
       | quasiquotes, which let you write code that gets turned into AST
       | that you can then process. Good macros. A programmable compiler
       | pipeline. So much. Alas, obscured now. https://boo-
       | language.github.io/
        
         | daeken wrote:
         | Boo and Nemerle both were really showing what was possible in
         | .NET back in the early days. I still miss the metaprogramming
         | they had, not to mention their pattern matching (which C# has
         | closed the gap on, but is still way, way short.)
        
         | andix wrote:
         | The syntax looks a bit like F#. But F# only has a few features
         | that generous people might consider meta programming.
         | 
         | There was a lot of fuss about meta programming around 10-15
         | years ago, but it never got a lot of traction. Maybe for a good
         | reason? I think a lot of the problems it solved, were also
         | solved by functional programming features that slowly appeared
         | in C# over the years.
        
       | rubenvanwyk wrote:
       | How is Comptime different from default AOT compilation? I assume
       | it can work with third-party libraries that AOT don't work with
       | yet?
        
         | betaporter wrote:
         | I believe this is like `constexpr` for C#.
        
         | andix wrote:
         | It's a code generator that runs during compile time. It's a
         | source generator that adds some generated code files to the
         | project. So it runs way before AOT or JIT. Once AOT/JIT run,
         | Comptime is already invisible to them, they only see the
         | generated code from Comptime.
        
       | spicyusername wrote:
       | I started using C# recently for a hobby project writing a game
       | engine on top of Monogame, and I have been very surprised at how
       | nice of a language C# is to use.
       | 
       | It has a clean syntax, decent package management, and basically
       | every language feature I regularly reach for except algebraic
       | data types, which are probably coming eventually.
       | 
       | I think the association of .NET to Microsoft tarnished my
       | expectations.
        
         | andix wrote:
         | Modern C# and .NET are great. It still suffers from the bad
         | reputation of the Windows-only .NET Framework. It's still a
         | quite heavy platform with a lot of features, but the .NET team
         | invested a lot of time to make it more approachable recently.
         | 
         | With top level Programs and file-based apps[1] it can be used
         | as a scripting language now, just add a shebang
         | (#!/usr/local/share/dotnet/dotnet run) to the first line and
         | make it executable. It will still compile to a temporary file
         | (slow on first run), but it doesn't require a build step
         | anymore for smaller scripts.
         | 
         | [1]: https://learn.microsoft.com/en-
         | us/dotnet/csharp/fundamentals...
        
           | actionfromafar wrote:
           | Also, if you compile Ahead Of Time (AOT) you can cut down on
           | the features and get basically as small a subset as you want
           | of the libraries. IMHO C# and dotnet are really starting to
           | become very impressive.
        
             | andix wrote:
             | AOT requires a lot of fiddling around, and might break the
             | application unexpectedly, with very weird errors. It is
             | mostly targeted to Blazor (WASM) and for serverless
             | functions.
             | 
             | The default runtime and JIT are fine for most use cases.
        
               | orphea wrote:
               | > AOT requires a lot of fiddling around, and might break
               | the application unexpectedly, with very weird errors.
               | 
               | It hasn't been my experience. Native AOT does come with
               | some limitations [1][2], but nothing awful. Mostly it's
               | that you can't generate code at runtime and you have to
               | tame the code trimmer. Just don't ignore code analysis
               | warnings and you should be good.                 > It is
               | mostly targeted to Blazor (WASM) and for serverless
               | functions.
               | 
               | Making your CLIs start fast is also a huge use case.
               | 
               | [1]: https://learn.microsoft.com/en-
               | us/dotnet/core/deploying/nati...
               | 
               | [2]: https://learn.microsoft.com/en-
               | us/dotnet/core/deploying/trim...
        
             | olvy0 wrote:
             | There's also bflat [0]. Not an official Microsoft product,
             | more of a passion project of a specific employee.
             | 
             | "C# as you know it but with Go-inspired tooling that
             | produces small, selfcontained, and native executables out
             | of the box." Really impressive. Self contained and small
             | build system.
             | 
             | [0] https://github.com/bflattened/bflat
        
         | thdrtol wrote:
         | I love C# and .NET and use it every day.
         | 
         | Sometimes I wonder if the .NET department is totally separated
         | from the rest of Microsoft. Microsoft is so bad on all fronts I
         | stopped using everything that has to do with it. Windows, Xbox,
         | the Microsoft account experience, the Microsoft store, for me
         | it has been one big trip of frustration.
        
           | andix wrote:
           | Microsoft is huge, it's many companies inside one company.
           | 
           | .NET seems to be somewhere close to Azure, but now far away
           | from Windows or the business applications (Office/Teams,
           | Dynamics, Power Platform). Things like GitHub, LinkedIn or
           | Xbox seem to be de facto separate companies.
           | 
           | Edit: .NET used to be tied closely to Windows, which gave it
           | the horrible reputation. The dark age of .NET ;)
        
         | vips7L wrote:
         | Unions have a proposal:
         | https://github.com/dotnet/csharplang/issues/9662
        
       | faithlv wrote:
       | This is amazing, after using Zig, I started to write exactly this
       | for C# too but never really had motivation to finish it.
        
       | orphea wrote:
       | * Supported return types:          * Collections: ..., List<T>,
       | ...          * Note: Arrays are not allowed as return types
       | because they are mutable. Use IReadOnlyList<T> instead.
       | 
       | I don't understand. Why is List<T> allowed then if it's mutable?
        
         | andix wrote:
         | Array also implements IReadOnlyList if I'm not mistaken.
         | 
         | I think C# doesn't really have immutable collections, they just
         | can be typecasted to IReadonly* to hide the mutable operations.
         | But they can always be typecasted back to their mutable base
         | implementation.
         | 
         | The only real immutable collections I know of, are F#s linked
         | lists.
        
           | maltalex wrote:
           | Immutable collections exit, they were just added later. See
           | System.Collections.Immutable:
           | 
           | https://learn.microsoft.com/en-
           | us/dotnet/api/system.collecti...
        
             | andix wrote:
             | I do a lot of .NET programming, and I've never seen them
             | getting used. :O
        
               | ygra wrote:
               | Roslyn, the C#/VB compiler, uses them extensively, but in
               | other code they're indeed quite rare.
        
             | zmj wrote:
             | Those collections are more like copy-on-write than actual
             | immutable. System.Collections.Frozen is the real thing.
        
           | randomNumber7 wrote:
           | One man's constant is another man's variable.
        
           | orphea wrote:
           | Adding to the sibling comment, there are also
           | Frozen{Dictionary,Set}: https://learn.microsoft.com/en-
           | us/dotnet/api/system.collecti...
        
       | torginus wrote:
       | While this looks cool, I don't see any code generation capability
       | in the examples or the tests, only compile time evaluation.
       | 
       | This is more like constrexpr than a macro system.
        
       | andix wrote:
       | Does this finally allow reading string literals from files and
       | include them into the binaries?
       | 
       | I've seen a Go project that was heavily using
       | https://pkg.go.dev/embed for loading some template files, and got
       | a bit jealous. Back in the days of .NET Framework it was common
       | to compile file contents into resource files, but it was always
       | quite cumbersome.
        
         | floucky wrote:
         | Can't you already do that with embedded resources?
        
           | andix wrote:
           | Yes, you can. But it's a bit cumbersome. The API to read them
           | is not really intuitive, they are only accessible as a
           | Stream, so they need to be either read every time into a
           | string (new allocation, slow), or you need a helper that
           | reads them once and keeps them in memory. I think there are
           | also a lot of gotchas around naming and listing them.
           | 
           | In modern code I don't see them that often anymore.
        
             | orphea wrote:
             | I see your point and I don't necessarily disagree but
             | better API for embedded resources is just a couple of
             | simple extension methods away. I wouldn't even bother
             | adding an external package for it.
        
         | eterm wrote:
         | C# / .NET has always had that abililty.
         | <EmbeddedResource Include="/path/to/file.txt" />
         | 
         | Then you can read that file from the assembly with:
         | assembly.GetManifestResourceStream
         | 
         | I'm not sure what's awkward about that, but it's nothing new.
         | 
         | Discoverability for this is in visual studio, if you go to file
         | properties there's a drop-down where you choose between
         | Content, EmbeddedResource, Ignore, etc.
         | 
         | That determines what happens in compile, whether it is copied
         | to output directory, compiled into the binary, etc.
        
           | andix wrote:
           | The Stream is awkward. It needs to be processed either once
           | at startup or lazy on access, or every time it's used
           | (allocations!). Also discovering multiple files is awkward.
        
       ___________________________________________________________________
       (page generated 2025-12-25 23:01 UTC)