TypeScript for C# Developers – making a move

TypeScript for C# Developers – making a move

Introduction

Moving from C# to TypeScript can be a smooth transition, especially compared to switching to a completely different programming paradigm, since both languages share a lot of similarities in syntax and features.

However, developers can still face several challenges and common issues due to the differences in the languages’ nature, ecosystem, and typical usage patterns. Here are some of the most common problems:

1. Understanding Dynamic Typing and Type Inference

While both C# and TypeScript support strong typing, TypeScript is more flexible due to its JavaScript heritage. TypeScript’s type system allows for dynamic typing and type inference, which can be unfamiliar for developers accustomed to the strictly-typed nature of C#.

  • TypeScript is More Forgiving: Variables in TypeScript can hold any type unless explicitly specified, and types can be inferred by the compiler, which can lead to unexpected behaviour if not properly understood.
  • Any Type: The any type in TypeScript can contain any JavaScript value. This is a powerful feature, but overuse can lead to the loss of the benefits of strong typing. Try to avoid this as much as possible.

2. Different Module Systems

TypeScript, being a superset of JavaScript, uses JavaScript’s module system, which is different from the namespace and assembly-based approach in C#.

  • Modules vs. Namespaces: In TypeScript, modules are files, and each file is a module if it contains at least one export or import statement. This approach is different from C#’s namespaces, and it might take time to get used to organising code in this way.

3. Asynchronous Programming Model

TypeScript (and JavaScript) heavily rely on asynchronous programming, especially for operations like network requests, file operations, or timers.

  • Promises and Async/Await: While C# has async and await, the way asynchronous programming is handled in TypeScript with promises can be initially confusing. Understanding how to work with Promise objects and the event loop is crucial.

4. Front-End Ecosystem Complexity

TypeScript is often used in the context of front-end development, which involves a different set of tools and frameworks compared to typical C# server-side development.

  • Tooling: The JavaScript ecosystem includes tools like npm, webpack, and various transpilers and bundlers, which can be overwhelming initially.
  • Frameworks and Libraries: Getting acquainted with front-end frameworks like React, Angular, or Vue.js, which are commonly used with TypeScript, requires additional learning.

5. Looser Typing in External Libraries

When using external JavaScript libraries in TypeScript, you might have to deal with less strict typing than you’re used to in C#.

  • DefinitelyTyped: TypeScript’s type definitions for JavaScript libraries (@types/*) can be incomplete or out of date, leading to challenges in integrating third-party libraries seamlessly.

6. Runtime Behavior Differences

TypeScript compiles down to JavaScript, and understanding JavaScript’s runtime behavior is essential, as it differs from C#’s CLR (Common Language Runtime).

JavaScript and TypeScript’s prototype-based inheritance is a fundamental concept that often poses a learning curve for developers accustomed to the classical inheritance model used in languages like C#. Understanding these differences is key to mastering object-oriented programming in these languages.

Prototype-based Inheritance

In prototype-based languages like JavaScript and TypeScript, inheritance works through a concept of ‘prototypes’—every object has a prototype, which is another object it inherits properties and methods from.

Key Concepts:

  1. Prototypes: Every JavaScript object has a prototype property, which is a reference to another object. When trying to access a property or a method of an object, if it’s not found on the object itself, JavaScript looks up the prototype chain until it either finds the requested property/method or reaches the end of the chain.
  2. Prototype Chain: This chain is a series of links between objects. If a property is not found on the current object, the engine looks at the object’s prototype, then the prototype’s prototype, and so on, until the property is found or the end of the chain is reached.
  3. Constructor Functions and new Keyword: In JavaScript, constructor functions are used in conjunction with the new keyword to create instances. The new operator creates a new object and sets its prototype to the prototype property of the constructor function.
  4. Object.create: This method creates a new object with a specified prototype.

const personPrototype = {
    sayHello() {
        return `Hello, my name is ${this.name}`;
    }
};

const jane = Object.create(personPrototype);
jane.name = 'Jane';
jane.sayHello(); // "Hello, my name is Jane"

This code shows constructor functions: –

function Person(name) {
    this.name = name;
}

Person.prototype.sayHello = function() {
    return `Hello, my name is ${this.name}`;
};

const john = new Person('John');
john.sayHello(); // "Hello, my name is John"

Classical Inheritance in C#

In contrast, C# uses classical or class-based inheritance, which is more straightforward and familiar to developers coming from a traditional object-oriented background.

Key Characteristics:

  1. Classes: C# uses classes as blueprints for objects. A class defines the shape and behavior (properties and methods) of objects.
  2. Inheritance: In C#, a class can inherit from another class, gaining its properties and methods. This is achieved using the : symbol.
  3. Base Class and Derived Classes: The concept of base (parent) and derived (child) classes is a fundamental aspect of classical inheritance. The derived class inherits from the base class.

The code below shows inheritance in C#: –

public class Person
{
    public string Name { get; set; }

    public string SayHello()
    {
        return $"Hello, my name is {Name}";
    }
}

public class Employee : Person
{
    public int EmployeeId { get; set; }
}

Employee emp = new Employee { Name = "John", EmployeeId = 123 };
emp.SayHello(); // "Hello, my name is John"

TypeScript for C# developers – Comparing the Two Models

  • Conceptual Difference: Prototype-based inheritance is more dynamic and less structured compared to classical inheritance. It focuses on objects and their prototypes, rather than classes and hierarchies.
  • Flexibility vs. Rigidity: Prototype-based inheritance offers more flexibility (e.g., you can modify an object’s prototype at runtime), but it can lead to less predictable and harder-to-manage code if not used carefully. Classical inheritance, being more rigid, provides a more structured and predictable approach.
  • Performance: In some cases, classical inheritance can be more performant due to optimisations possible in class-based systems. However, JavaScript engines are highly optimised for prototype-based inheritance.
  • Scope and Closures: Concepts like closures, hoisting, and the scope of variables (let vs. var) are different in TypeScript/JavaScript.

7. Lack of Some C# Features

Some features in C#, such as LINQ or certain language-specific syntactic sugar, don’t have direct equivalents in TypeScript.

TypeScript, being a superset of JavaScript, inherits the array methods provided by JavaScript. These methods include filter, map, reduce, forEach, find, and more. While these functions cover many common data manipulation needs, they don’t quite match the full range of capabilities offered by LINQ in C#.

1. Method Availability and Chaining

  • C# LINQ: LINQ offers a wide variety of methods for querying and manipulating data, such as Where, Select, OrderBy, GroupBy, Join, and more. LINQ queries can be elegantly chained and are lazily executed, which means the actual operation is deferred until the result is enumerated. This lazy execution is beneficial for efficiency, especially with large datasets.
  • TypeScript Arrays: TypeScript array methods cover many similar functionalities but with fewer options. For instance, you can chain map (similar to Select), filter (similar to Where), and reduce, but there are no direct equivalents for more complex operations like GroupBy or Join. Also, array methods in TypeScript execute immediately, not lazily.

2. Query Syntax and Readability

  • C# LINQ: LINQ allows you to write queries in a SQL-like query syntax, making them very readable, especially for those familiar with SQL. For example, querying a collection of objects in C# using LINQ can look very similar to a SQL query.
  • TypeScript Arrays: TypeScript requires chaining method calls, which, while still readable, can become less intuitive when performing complex operations. The lack of a SQL-like query syntax means that equivalent operations might be less concise and more imperative in nature.

3. Type Safety and IntelliSense

  • C# LINQ: LINQ is fully integrated into C#, meaning it benefits from the language’s strong typing and IntelliSense in IDEs like Visual Studio. This integration helps catch errors at compile time and provides excellent tooling support for writing queries.
  • TypeScript Arrays: While TypeScript provides type safety to JavaScript, some of the type inferencing with array methods can be less straightforward compared to LINQ in C#. Tooling support, although good, might not always match the level of assistance provided by LINQ in C#.

4. Performance Considerations

  • C# LINQ: LINQ queries are known for their efficiency in handling large datasets, especially with IQueryable providers like Entity Framework, which translates LINQ queries into optimised SQL queries for databases.
  • TypeScript Arrays: Performance in TypeScript/JavaScript heavily depends on the runtime environment (like V8 in Chrome). For handling large datasets, you might need to optimise your approach more manually compared to using LINQ.

Conclusion

Transitioning from C# to TypeScript involves adapting to a different ecosystem, programming style, and toolset.

The key is to understand the fundamental differences, especially in terms of type system, module organisation, asynchronous programming patterns, and runtime behaviour.

Embracing these differences will lead to a more effective and enjoyable development experience in TypeScript.

The official TypeScript documentation can be found here: – https://www.typescriptlang.org/docs/

I have other related posts here: – Building a Web API with TypeScript, Using Jest With TypeScript.

There is also a great post about TypeScript for C# developers here: – https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-oop.html

Stephen

Hi, my name is Stephen Finchett. I have been a software engineer for over 30 years and worked on complex, business critical, multi-user systems for all of my career. For the last 15 years, I have been concentrating on web based solutions using the Microsoft Stack including ASP.Net, C#, TypeScript, SQL Server and running everything at scale within Kubernetes.