Let’s elaborate on the differences between TypeScript and C# in terms of class definitions, focusing on their structure, access modifiers, static constructors, abstract classes, and additional features like sealed classes in C#. I’ll provide clear examples to illustrate each point and highlight how these differences manifest in practice.
TypeScript: Classes
TypeScript classes are built on JavaScript’s ES6 class syntax, enhanced with static typing and features like access modifiers. They are designed to work in JavaScript environments (browsers or Node.js), and their type-related features (like access modifiers) are enforced only during compilation, as they are erased in the generated JavaScript.
1. Class Structure and Type Annotations
TypeScript classes resemble JavaScript ES6 classes but include type annotations for properties, methods, and parameters. They support constructors and inheritance.
- Example:
class Person { name: string; // Type annotation for property age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } greet(): string { return `Hello, I'm ${this.name}, ${this.age} years old`; } } const person = new Person("Alice", 30); console.log(person.greet()); // Output: Hello, I'm Alice, 30 years old
2. Access Modifiers (public, private, protected)
TypeScript supports public
, private
, and protected
access modifiers, but these are enforced only at compile-time. After compilation to JavaScript, these modifiers disappear, so runtime access is not restricted.
- Example:
class Employee { public name: string; private salary: number; // Private: only accessible within the class protected department: string; // Protected: accessible in class and subclasses constructor(name: string, salary: number, department: string) { this.name = name; this.salary = salary; this.department = department; } getDetails(): string { return `${this.name} works in ${this.department} with salary ${this.salary}`; } } class Manager extends Employee { getDepartment(): string { return this.department; // Protected property is accessible in subclass } } const emp = new Employee("Bob", 50000, "IT"); console.log(emp.name); // OK: public // console.log(emp.salary); // Error: Property 'salary' is private console.log(emp.getDetails()); // Output: Bob works in IT with salary 50000 const manager = new Manager("Alice", 80000, "HR"); console.log(manager.getDepartment()); // Output: HR
- Note: The
private
modifier preventsemp.salary
access at compile-time, but in the compiled JavaScript,salary
is just a regular property, so runtime access is possible (e.g., viaemp['salary']
).
- Note: The
3. No Static Constructor
TypeScript does not support static constructors (a special method to initialize static members). Static properties and methods are supported, but initialization must be done inline or within regular methods.
- Example:
class Counter { static count: number = 0; // Static property initialized inline constructor() { Counter.count++; } static getCount(): number { return Counter.count; } } const c1 = new Counter(); const c2 = new Counter(); console.log(Counter.getCount()); // Output: 2
4. Abstract Classes
TypeScript supports abstract
classes, which cannot be instantiated and can define abstract methods that subclasses must implement.
- Example:
abstract class Animal { abstract makeSound(): string; // Abstract method move(): string { return "Moving..."; } } class Dog extends Animal { makeSound(): string { return "Woof!"; } } const dog = new Dog(); console.log(dog.makeSound()); // Output: Woof! console.log(dog.move()); // Output: Moving... // const animal = new Animal(); // Error: Cannot create an instance of an abstract class
C#: Classes
C# classes are a core part of the language, deeply integrated with the .NET ecosystem. They support strict compile-time and runtime enforcement of access modifiers, static constructors, abstract classes, and sealed classes, reflecting C#’s strongly-typed, object-oriented design.
1. Class Structure and Explicit Typing
C# classes require explicit type declarations for properties, fields, and methods. They are typically defined within namespaces and compiled to Intermediate Language (IL) for the .NET runtime.
- Example:
public class Person { public string Name { get; set; } public int Age { get; set; } public Person(string name, int age) { Name = name; Age = age; } public string Greet() { return $"Hello, I'm {Name}, {Age} years old"; } } class Program { static void Main() { Person person = new Person("Alice", 30); Console.WriteLine(person.Greet()); // Output: Hello, I'm Alice, 30 years old } }
2. Access Modifiers (public, private, protected, internal)
C# enforces access modifiers (public
, private
, protected
, internal
) at both compile-time and runtime, ensuring encapsulation. The internal
modifier restricts access to the same assembly.
- Example:
public class Employee { public string Name { get; set; } private int salary; // Private: only accessible within the class protected string Department { get; set; } // Protected: accessible in class and subclasses internal string Company { get; set; } // Internal: accessible within same assembly public Employee(string name, int salary, string department, string company) { Name = name; this.salary = salary; Department = department; Company = company; } public string GetDetails() { return $"{Name} works in {Department} with salary {salary}"; } } public class Manager : Employee { public Manager(string name, int salary, string department, string company) : base(name, salary, department, company) { } public string GetDepartment() { return Department; // Protected property is accessible } } class Program { static void Main() { Employee emp = new Employee("Bob", 50000, "IT", "TechCorp"); Console.WriteLine(emp.Name); // OK: public // Console.WriteLine(emp.salary); // Error: 'salary' is private Console.WriteLine(emp.Company); // OK: internal within same assembly Console.WriteLine(emp.GetDetails()); // Output: Bob works in IT with salary 50000 Manager manager = new Manager("Alice", 80000, "HR", "TechCorp"); Console.WriteLine(manager.GetDepartment()); // Output: HR } }
- Note: Unlike TypeScript, C#’s access modifiers are enforced at runtime, so attempting to access
salary
outside the class (e.g., via reflection) would require explicit permission or workarounds.
- Note: Unlike TypeScript, C#’s access modifiers are enforced at runtime, so attempting to access
3. Static Constructors
C# supports static constructors, which are used to initialize static members of a class. They run once, automatically, before any static members are accessed or instances are created.
- Example:
public class Counter { public static int Count; static Counter() // Static constructor { Count = 0; Console.WriteLine("Static constructor called"); } public Counter() { Count++; } public static int GetCount() { return Count; } } class Program { static void Main() { Counter c1 = new Counter(); Counter c2 = new Counter(); Console.WriteLine(Counter.GetCount()); // Output: 2 } }
- Output:
Static constructor called 2
Count
is initialized before use, and it runs only once. - Output:
4. Abstract Classes
C# supports abstract classes, which cannot be instantiated and can define abstract methods that derived classes must implement.
- Example:
public abstract class Animal { public abstract string MakeSound(); // Abstract method public string Move() { return "Moving..."; } } public class Dog : Animal { public override string MakeSound() { return "Woof!"; } } class Program { static void Main() { Dog dog = new Dog(); Console.WriteLine(dog.MakeSound()); // Output: Woof! Console.WriteLine(dog.Move()); // Output: Moving... // Animal animal = new Animal(); // Error: Cannot create an instance of the abstract class } }
5. Sealed Classes
C# supports sealed classes, which prevent further inheritance. This is useful for ensuring a class cannot be extended, often for security or design reasons.
- Example:
TypeScript has no equivalent to
public sealed class FinalClass { public string GetMessage() { return "This class cannot be inherited"; } } // public class Derived : FinalClass {} // Error: Cannot derive from sealed class class Program { static void Main() { FinalClass obj = new FinalClass(); Console.WriteLine(obj.GetMessage()); // Output: This class cannot be inherited } }
sealed
classes, as JavaScript’s prototype-based inheritance doesn’t enforce such restrictions.
Key Differences Summarized with Examples
-
Class Structure:
- TypeScript: Classes are based on ES6 JavaScript, with type annotations for properties and methods. They transpile to JavaScript, losing type information at runtime.
class Person { constructor(public name: string, public age: number) {} // Shorthand for property declaration }
- C#: Classes are strongly-typed, compiled to .NET IL, and retain type information at runtime.
public class Person { public string Name { get; set; } public int Age { get; set; } public Person(string name, int age) { Name = name; Age = age; } }
- TypeScript: Classes are based on ES6 JavaScript, with type annotations for properties and methods. They transpile to JavaScript, losing type information at runtime.
-
Access Modifiers:
- TypeScript:
public
,private
,protected
are compile-time only, erased in JavaScript.class Test { private x: number = 10; } const test = new Test(); console.log(test['x']); // Works at runtime despite private modifier
- C#: Access modifiers are enforced at compile-time and runtime.
public class Test { private int x = 10; } Test test = new Test(); // Console.WriteLine(test.x); // Error: 'x' is inaccessible due to its protection level
- TypeScript:
-
Static Constructors:
- TypeScript: No static constructors; static properties are initialized inline or in methods.
class StaticTest { static value: number = 0; }
- C#: Supports static constructors for one-time initialization of static members.
public class StaticTest { public static int Value; static StaticTest() { Value = 0; } }
- TypeScript: No static constructors; static properties are initialized inline or in methods.
-
Abstract Classes:
- TypeScript: Supports abstract classes with abstract methods, enforced at compile-time.
abstract class Shape { abstract draw(): string; }
- C#: Supports abstract classes with runtime enforcement, using
override
for method implementation.public abstract class Shape { public abstract string Draw(); }
- TypeScript: Supports abstract classes with abstract methods, enforced at compile-time.
-
Sealed Classes:
- TypeScript: No concept of sealed classes; inheritance is always possible unless manually restricted (e.g., using
final
in JavaScript with custom logic).class MyClass {} class Derived extends MyClass {} // Always allowed
- C#: Supports
sealed
classes to prevent inheritance.public sealed class MyClass {} // public class Derived : MyClass {} // Error: Cannot inherit from sealed class
- TypeScript: No concept of sealed classes; inheritance is always possible unless manually restricted (e.g., using
Difference Table
Feature | TypeScript | C# |
---|---|---|
Class Foundation | Based on JavaScript ES6 classes, with type annotations. | Core to the .NET ecosystem, strongly typed and object-oriented. |
Access Modifiers | public , private , protected ; enforced at compile-time only (erased in JS). | public , private , protected , internal ; enforced at compile-time and runtime. |
Constructor | Standard constructor; parameter properties shorthand (public name: string ). | Standard constructor; no parameter properties shorthand. |
Static Constructor | Not supported; static properties initialized inline or in static methods. | Supported with static constructor for initializing static members. |
Abstract Classes | Supported with abstract keyword; abstract methods defined without implementation. | Supported with abstract keyword; requires override in derived classes. |
Sealed Classes | Not supported; all classes can be extended unless restricted via other means. | Supported with sealed keyword to prevent inheritance. |
Runtime Behavior | Types and access modifiers erased at runtime (JavaScript output). | Types and access modifiers preserved at runtime (IL in .NET CLR). |
Property Declaration | Properties can be declared with type annotations; optional initialization. | Properties typically use getters/setters or auto-implemented properties. |
Inheritance | Single inheritance; uses structural typing for polymorphism. | Single inheritance; uses nominal typing with virtual /override for polymorphism. |
Example | typescript<br>class Person {<br> private name: string;<br> constructor(name: string) { this.name = name; }<br>}<br> | csharp<br>public class Person<br>{<br> private string name;<br> public Person(string name) { this.name = name; }<br>}<br> |
Summary
- TypeScript classes are lightweight, JavaScript-based, and designed for flexibility in web development. Access modifiers are compile-time only, and there’s no static constructor or sealed class support, reflecting JavaScript’s dynamic nature.
- C# classes are robust, with strict runtime enforcement of access modifiers, support for static constructors, and features like sealed classes, tailored for the .NET ecosystem’s object-oriented, type-safe design.
These differences make TypeScript suitable for dynamic, JavaScript-heavy environments, while C# excels in structured, enterprise-grade applications with strong runtime guarantees.