Why C# goes well with TypeScript
nate.org
nate.org
In particular, if TypeScript isn't performant enough for whatever I'm trying to do, then it seems likely that there'll be more performance bottlenecks in the future, and so just moving to a generically faster language won't be good enough; I need a language that offers precise control over performance, so that when future issues come up, I know I'll be able to solve them. C# sort of has this with unsafe, but it's a lot rougher than C++/Rust, which is presumably why the article doesn't mention it.
I could see using C# instead of TypeScript on the server, with TypeScript reserved for the web client only, but that's quite a different proposition. And if I were starting a new codebase from scratch for, say, a web app, I'd just use TypeScript the whole way down, both for homogeneity and to make it easier to take advantage of server-side rendering, isomorphic business logic, etc., and drop down to C++/Rust (via node-addon-api/napi-rs on the server or WebAssembly in the browser) if this proved to be necessary for performance.
* I'd much rather use Rust, but it's definitely the case that if you need to integrate with existing C++ code, no other language today can do that very well. I'm hoping that Rust will be able to do so in the future, so that legacy C++ apps can be ported incrementally, instead of requiring each app (or at best each large component of an app, as in Firefox) to be rewritten all at once. C++-to-Rust will probably never be quite as smooth as JavaScript-to-TypeScript or Java-to-Kotlin or Objective-C-to-Swift, but it might one day be good enough that the benefits outweigh the costs.
> I just can't see having three different general-purpose languages in my stack for a single app
What about for a larger system (comprised of multiple projects) or for a company? I'll give an example. I'm currently developing a cloud-based product to allow multiple people to remotely view and control the same web browser (mostly for virtual and augmented reality). Performance is important due to the real-time communication aspect. The part that interfaces with Chromium is C++, but I want to write as much of the services as possible in a higher level language to speed up development and avoid memory-related footguns. So, I chose C# for the services in the hot path because it's more performant than Node and it's also easier to call into C++. However, the system is really comprised of multiple services, and I still use TypeScript + Node for parts that aren't performance critical, like lambda functions that control autoscaling and the customer-facing web panel.
> In particular, if TypeScript isn't performant enough for whatever I'm trying to do, then it seems likely that there'll be more performance bottlenecks in the future, and so just moving to a generically faster language won't be good enough; I need a language that offers precise control over performance, so that when future issues come up, I know I'll be able to solve them.
The tradeoff with going all-in on C++ or Rust (for me at least) is that it significantly slows down development. For me, it's really important to be able to move quickly when developing a new product, both because I want to get it to market quickly and because I know I'm going to have to make changes based on what I learn from the market. I can still move fast with C#, and I don't find having a third language as a significant burden (like I mention in the article, I think it's a lot like TypeScript). Also, since it interoperates with C++ easily, that makes it easier to move more pieces to native code in the future if needed.
That said, I still don't really understand why you'd use TypeScript on the server at all if you need C# for other services; the advantages (faster builds?) don't seem sufficient to cover the costs of additional investment in tooling, inability to share common code, etc. (even assuming that training and/or cognitive-switching costs aren't an issue). Unless you're running the same code both on the server and in the browser; in that case I would probably dispense with C# but I could see someone choosing the three-different-languages approach, at least until .NET's in-browser story gets better.
For a company with multiple unrelated projects, it'd be a judgment call, weighing the benefits of not being locked into a single ecosystem against the costs of losing economies of scale. I don't think I have a good general principle.
I'm also not super clear on how well C# interoperates with C++; I've used both languages, but not together. The article makes it sound like it only "just works" if the entire API surface is C-compatible, which seems extremely limiting and not like something I would want to do regularly. In particular, it sounds like it'd be necessary to write wrappers on both ends, so that you have idiomatic C++ or C# everywhere except at the boundary. This is what I meant above about C++-to-Rust interop not being good enough yet—it works, but you have to go through C. Is this in fact what it's like, or can C# code automatically use C++ APIs that involve classes, RAII, templates, etc.?
I don't advocate going all-in on C++ or Rust for a typical app, but rather having a setup that allows easily dropping into it in order to optimize hot paths. Performance tuning in a managed runtime is really hard and I prefer to avoid it.
Yeah, they're frequently used for that. In the case of my cloud service, though, there are multiple pieces that are beneficial to scale independently (browser servers, WebRTC servers, functions for orchestration, and functions for the user-facing web-based administration). I also want to put as much onto AWS Lambda as possible to simplify scaling, but the stateful browser and WebRTC services can't run on Lambda.
> That said, I still don't really understand why you'd use TypeScript on the server at all if you need C# for other services
That's fair. One reason is that I feel that I develop the fastest with TypeScript. Another is that I develop the web front-end in TypeScript and like being able to share code for the web-related parts. Another is that I like to leverage AWS Lambda for the non-stateful parts, and Lambda's cold start times for Node are lower than those for .NET, which can help provide lower latency for user-initiated web requests.
> In particular, it sounds like it'd be necessary to write wrappers on both ends, so that you have idiomatic C++ or C# everywhere except at the boundary.
Yeah, you're right. The parts that C# calls must C linkage, so C++ functions must be wrapped in `extern "C"`. My understanding is that C is the lingua franca between languages because it has a simple ABI, whereas the ABI for C++ is complex and may between compilers.
Is there really more to say than that?
(Also, use of mostly functions with relatively few classes is common in, e.g., modern React; I don't think you're fighting the language too much if you do it.)
Lots of languages compile to other languages with different runtimes.
Meanwhile you have the option of specifying a language neutral protocol for invoking public interfaces across languages, or reuse a wire protocol. Not many people seem to make this tradeoff though, it would usually be a bigger pain than to live with the limitations of cross language direct calls.
(Also, I've definitely seen Protocol Buffers used successfully as a means of cross-language interop. But I suppose this works better if you already have really strong tooling support for them.)
It shouldn't be a big deal (theoretically) to transpile C# to JS (though you'd have to provide interfaces for any existing JS code you'd like to interact with).
The other way - JS->C# - is a lot hairier.
Compare compiling a typed language to bytecode vs "decompiling" from bytecode to an unavoidable plate of spaghetti.
A bit too foggy right now to have any valid thoughts on reflection, but I intuitively imagine strings can be abused to that end.
[0] https://stackoverflow.com/questions/16434389/javascript-and-...
Any type information required at runtime can be encoded into a string.
Javascript is Turing-complete and therefore, fundamentally, any valid C# program has at least one JS representation.
(I'm not saying any of this will be efficient, a great idea or experience; just that it's possible)
If your codebase is entirely C# and you're just compiling it to JS so that it can run in browsers, then the compiler can fix this by inserting copies where needed. (The Turing-completeness of JavaScript implies that something like this must be possible.) However, if you want to be able to freely call back and forth between C# code and JavaScript code that doesn't know about this calling convention, while maintaining the full semantics of both languages, then you are going to run into problems. That's what I'm claiming is hard.
There's no conceptual barrier to reflection; the problem is just that the required type information might be a lot of bytes, and it might be hard to tell for sure whether it's actually used at runtime. You might also have to ship a lot of support code that emulates enough of the CLR to make these operations work. So there'd be a strong temptation to just not support these features, which then would cause dependencies to break unpredictably.
Ah. You’re talking about something different here, hence the confusion. Here’s where I came in:
> I would like for c# to compile to TS or JS with the same support as ts to js.
> I don't think this can work.