Back in 2021 [I started a discussion](https://community.haxe.org/t/some-notes-fe…edback-about-c-target/3009/8) to share how much I didn't want the C# target to go away, despite all its flaws.
Time has passed and there have been some efforts with @SomeRanDev to create a new C# target [using Reflaxe](https://github.com/SomeRanDev/reflaxe.CSharp). It's been promising but more a proof of concept than anything really usable. Being busy on other matters that are more important that this, and knowing Haxe 4 C# target is still usable currently didn't help either.
Then, more time has passed and tools have evolved in a way that it became less scary, and more importantly less time consuming, to dig into Haxe compiler itself and its OCaml generators, and to start creating a whole new target from the grounds up, following the patterns of the existing targets and running it over the full test suite.
So that's what I did: I started creating a **new C# target for Haxe 5**, trying my best to follow the patterns of the existing targets (main reference being the JVM target, but also the previous Haxe 4 C# target, which does give a lot of C# specific insights).
I tried not to repeat the mistakes of the previous C# target: it is mostly self contained and it generates an intermediate C# AST (which follows an advice from @Simn I kept in mind when we were discussing about Reflaxe/CSharp which was doing "direct to text" transpilation at the time).
Although I can navigate through it and am familiar with pattern matching and manipulating ASTs, I am not particularly fluent in OCaml, but with the help of a coding assistant (Opus 4.5, mostly), it has been possible to iterate, take design decisions, write, review and test the code generation in a way that allows to converge to something that works. The `gencs.ml` file is quite large, which could be because it has a lot of comments (perks of using tooling that can write those easily, and makes the result more educational, for me at least), but maybe also because there are some redundancies, although I really tried to avoid them. C# being particularly picky regarding its type semantics could also be another reason a lot of patterns need to be handled, thus more code, but likely, there is room for improvements there as the priority has been correctness of the output and not necessarily keeping the OCaml concise.
Anyway, I am opening this Draft Pull Request today because I reached an important milestone: all `unit`, `sys` and `threads` tests are now passing correctly, so I believe it's a good time to start discussing about this, and question whether merging a new C# target to haxe could be considered, or if this new C# target is doomed to stay in a fork.
What is pretty clear on my end is that, merged or not, this work will be used for [Ceramic](https://ceramic-engine.com) to bring its Unity target to Haxe 5 (but also other projects I have that involve Haxe and C#), and any additional work needed to keep the target working with Haxe 5 in the future will be done : in other words I plan to maintain this as seriously as I maintain other projects like Ceramic in the forseeable future.
Some design choices on this target, that may be sometime very close, sometimes different from the previous one:
### Nullable types (`Null<T>`, optional params, in haxe) are transformed following those rules:
- Types that are inherently nullable (objects / reference types / strings ...) are not wrapped into anything, given that they are compatible with `null`
- Value/Primitive types are wrapped into [`haxe.lang.Null<T>`](https://github.com/jeremyfa/haxe/blob/new_csharp/std/cs/_cs/haxe/lang/Null.cs) (struct, on the stack, not the heap), and unwrapped as needed.
- C# generator takes care of generating proper coercion (using `.hasValue` instead of `!= null`, or calling proper methods to wrap/unwrap). The struct itself support some implicit conversions, although it is most of the time made explicit by the generator.
### Functions / Closures
- They are all subclasses of `haxe.lang.Function`
- Those functions can be called without boxing, thus without allocations on the heap (even for primitive types), because arguments are passed through a [`haxe.lang.Value`](https://github.com/jeremyfa/haxe/blob/new_csharp/std/cs/_cs/haxe/lang/Value.cs) struct (16 bytes) that can hold `bool`, `int`, `float`, `double`, `long`, any object including `null` or "no value", which also cover things like optional arguments.
- A caching mecanism is in place so that if you read `myObj.someMethod`, it will always return the same `haxe.lang.Function` instance, meaning you can use regular object equality to compare methods.
### AOT / NativeAOT
- The generated code is designed to be AOT compatible, even when using haxe reflection features, and is expected to work, on situations where C# reflection wouldn't be available. All the data needed to support haxe reflection is embedded in the related types. This could be fined-tuned in the future to allow disabling this additional data generation, if the code is expected to work in a scenario that does have full C# reflection available, but for now it is embedded by default, unless using `@:unreflective` meta (like in cpp target).
### Generics
- One of the trickiest parts of this target are generics, because Haxe generics are much more forgiving and flexible than C# generics. You can cast `SomeClass<Animal>` to `SomeClass<Dog>`, or even `SomeClass<Dynamic>` in Haxe, but this would just trigger a runtime error in C#.
- Haxe 4's C# target had a `-D erase-generics` define to remove generics. In this new C# target, this is somewhat the default: all haxe generics are erased and `object` is used instead, which is good to simplify the code generation and play better with AOT, as long as the generic type is a class type or a string, but value types in generics will be boxed (e.g. `MyType<Int>`). That being said, if a value type generic is needed, one can use `@:generic` to end up with `MyType_Int` with the proper type and no boxing, so given that Haxe provides this, we don't really need to rely on C# to do the same!
### Arrays
- Thanks to @tobil4sk for pointing me to [this issue](https://github.com/HaxeFoundation/haxe/issues/4872), which helped me decide how to implement arrays in the C# target. Basically it follows this locking idea from @ncannasse, where arrays are of "unknown" type at first, then get locked to a specific type if casted to an explicit `Array<Int>`, `Array<Float>` etc... The whole thing is implemented in a single `Array` class which switch storage as needed using native C# arrays (`int[]`, `double[]` etc...).
### Dynamic objects
- For now, dynamic objects, anonymous structures, rely on a `Dictionary<string,object>` lookup to resolve values for keys. Previous C# target was precomputing hash code to make the field access faster. There is no such thing on this new C# target at the moment, but I'll address this at some point.
### .NET externs
- At the moment, there is no such feature as `-net-lib someLib.dll`, but I created a generator, written in C#, that outputs Haxe externs for C#, from a DLL (api) + XML (docs) couple. The C# target comes by default with .NET Standard 2.1 API externs which have been generated this way. The externs are documented with proper doc comments fetched from the XML that comes with the DLL. There is no need to use a separate haxelib like `hxcs` or any dll /dotnet setup to export C# from this target. The same generator can be use to generate externs from any DLL, it's just that it is not handled by haxe.
- Not against supporting `-net-lib` later in a similar fashion as the previous C# target, but it is not a priority for me right now, and not gonna lie, it's actually nice to be able to navigate through the generated externs directly. Ideal scenario would be to have both options of course.
### Interoperability with C#
- It is also not a priority to make the generated code interoperable as is with regular C#. My main priority is to allow to perfectly run a Haxe project from a C# runtime (dotnet, Unity...).
- So at the moment, there is no such thing as `@:nativeGen` to make the output more "C# friendly". This, or a similar feature could be considered in the future, however.
### C# exported as source / text
Being able to export actual C# code (vs a DLL) is intended. It makes it easier to generate code that targets third party APIs without needing to reference the related DLLs. For instance, in the case of Unity, you can simply drop the output C# and let Unity Editor build it, and you can even use C# conditional compilation (in addition to the haxe one), whereas if you wanted to export a DLL that references Unity API, you'd need to figure out first how to link with the DLLs that provide the correct APIs, or write stubs that accurately replace it. And you can always make a DLL from that C# output if needed.
---
There are still a lot of things to do and review, before considering any of this ready to use : I need to make sure there is nothing stupid in the generated code that would destroy performances, catch any remaining edge case that may not be covered by the unit tests, and the target needs to be tested and profiled on actual use cases (like Ceramic's Unity export as mentioned earlier).
Regardless, I'm interested to have your feedback on this new C# target work. What do you think? I'm personally excited to finally be able to output C# from Haxe > 4 in the foreseeable future.