Why do you say so?
Although you don't need to remove GC for everything. IDisposable already covers explicit destruction and the memory mapping can be handled with native functions where necessary. (I don't know if that's possible in bflat, I'm talking in general - but pinvoke should be enough)
See the whole linage of Mesa/Cedar, Modula-2+, Modula-3, Oberon, Midori,... OSes.
C#, like those languages, allows for manual memory management, unsafe code, stack allocation. MSIL was designed to also support languages like C++, and since C# 7, many of those capabilities are no longer only available to C++/CLI, rather being incremently exposed to C# as well.
Besides being a GC algorithm anyway, RC has performance issues, which can only be solved by using multiple optimization techniques from tracing GC, like background threads for cascading deletion of complex graphs, deferred counter manipulation, lock free data structures,...
Project Oberon source code,
Active Oberon source code (the latest language OS from the Oberon tree still being actively developed),
https://gitlab.inf.ethz.ch/felixf/oberon
It would be great if Microsoft some day releases Midori source code.
Waiting for this since years. But it won't happen, I guess, as it didn't happen until now.
Also: doing this means as soon as you start using predictable finalisation to close DB connections, or anything, your code stops being C# code. It can no longer be used except in a highly nontrivial environment. It depends, in an undeclared manner, on a nonstandard runtime.
So for open source code, or anything you're hoping to reuse: please don't do this.
If you want this, switch to C++, Swift or Rust, or some other language that has it built in.
Memory deallocation can be done as deterministic as malloc/free via the native heap APIs.
https://en.wikipedia.org/wiki/Singularity_(operating_system)
Like others said here, it could certainly be done in C#, but you have to fight the language on some level. Events/delegates, strings and almost all collection classes are heap based and therefore can trigger the GC. It's difficult to do useful stuff without them.
But you can do what you'd do in typical hard real-time applications anyway anyway, that is, preallocate everything you think you'll need. The APIs are there.
The most problematic data type is the string, it's almost impossible to do anything in C# without it, and it's in the heap, therefore can trigger the GC. Also it's immutable, so almost all useful operations on it produce another string(s). See Joe Duffy's old blog post here [0].
However Using the new Span<char> or ReadOnlySpan<char> which are stack based can alleviate that. Almost all string actions can be done on them. There's also a library called ZString which can be useful [1].
Another problem is the jit, which in "regular" dotnet works lazily, only on the first call to a function or even to string literals, so again you don't get deterministic execution. It is very quick but calling a new function chain in succession can add up. However using an AOT compilation like bflat does eliminates that problem.
Even without using AOT, you can approximate it by using reflection and calling RuntimeHelpers.PrepareMethod() on all methods in all referenced assemblies (except the system ones which should already be precompiled).
[0] https://github.com/joeduffy/joeduffy.github.io/blob/master/_...
https://www.ptc.com/en/products/developer-tools/perc
https://www.aicas.com/wp/products-services/jamaicavm/
It just happens that .NET never went down this path.