TypeScript For C Sharp Programmers Final
TypeScript For C Sharp Programmers Final
TypeScript
for C# Programmers
TypeScript for C#
Programmers
1
About the Author
Steve Fenton has been working with JavaScript for over ten
years as part of large-scale business-critical applications. He
has dabbled with Google's Dart language and flirted with
CoffeeScript but never really found a compelling reason to
switch away from writing raw JavaScript – until TypeScript
was announced to the world on October 1 st 2012. The
TypeScript language specification, version 0.8, was the first
addition to the collection of JavaScript futures that didn't ask
programmers to abandon JavaScript itself – it embraced the
good parts of the language and added a few neat extras that
make things more productive. Steve wrote the first book on
the TypeScript programming language and continues to use
it daily.
2
Table of Contents
About the Author 2
Table of Contents 3
Acknowledgements 5
Introduction 6
Compiling or Transpiling 9
Language Features 11
TYPESCRIPT FILES 11
TYPES 14
MODULES, CLASSES AND INTERFACES 17
FUNCTIONS 20
ENUMERATIONS 25
GENERICS 26
Structural Typing 29
Access Modifiers 33
Memory Management 36
RELEASING RESOURCES 39
Exceptions 41
Arrays 44
3
Dates 49
NOW 50
DATE METHODS 50
Events 52
MOUSE EVENTS 52
KEYBOARD EVENTS 53
OBJECT EVENTS 53
FORM EVENTS 54
CUSTOM EVENTS 54
RUNNING ORDER 55
Framework 57
Creating Definitions 60
DYNAMIC DECLARATIONS 60
TYPE DECLARATIONS 61
Useful Tricks 64
4
Acknowledgements
I would like to thank my family for constantly supporting me
when I do projects in my spare time.
I am grateful to InfoQ for giving me the opportunity to reach
a new audience, to Ana-Maria Ciobotaru for handling all my
questions and Jonathan Louis Allen for reviewing the book.
I would like to thank Mark Jones for providing a sounding
board for ideas, proof-reading my drafts and for being my
hardware-hacking partner. And finally my thanks go to Ryan
Cavanaugh, who has been tirelessly helping TypeScript early
adopters on Stack Overflow and is always ready to give
accurate and patient answers to the endless stream of
questions.
TypeScript is a trademark of Microsoft Corporation.
5
Introduction
If you are a .NET programmer working with JavaScript, the
emerging TypeScript language will be of great interest.
TypeScript introduces optional static typing and class-based
object orientation with the intention of making scalable
high-performance programs that run in the browser or on
the server.
Many of the features in the first version of TypeScript are
inspired by the ECMAScript 6 specification, which means
they will eventually be available in vanilla JavaScript. For
these, TypeScript gives early access to language features that
won’t be available in all browsers for some time. On top of
these, recent versions of the TypeScript language
specification have added features that may never be natively
supported, such as generics.
TypeScript code is compiled into plain JavaScript, which
means it can run in any web browser or on a server running
technology such as Node.js. The compilation process
performs transformations from TypeScript into idiomatic
JavaScript and erases annotations and interfaces intended
for compile-time checking. The process of removing type
information at compile time is termed type erasure.
6
Type Erasure
var name: string = "Steve Fenton";
7
You can follow the project on Codeplex to keep up to date
with changes, join in the discussion on the language preview
and file any bugs you find while using TypeScript.
http://typescript.codeplex.com/
8
Compiling or Transpiling
Despite the fact the term "transpiling" has been around
since last century, there is some confusion about what it
means and what the difference is between transpiling and
compiling.
Transpilation is actually a specific kind of compilation.
Compiling is the general term for taking source code written
in one language and transforming into another, whereas
transpiling is a specific term for taking source code written
in one language and transforming it into another language
with a similar level of abstraction.
When you compile C#, your method bodies are transformed
by the compiler into IL. This cannot be called transpiling
because the two languages are very different levels of
abstraction. When you compile TypeScript, it is transformed
by the compiler into JavaScript. These are very similar levels
of abstraction, so this is called transpiling.
Both compilers and transpilers can optimise the code as part
of the process.
Other common combinations that can be dubbed as
transpiling include C++ to C, CoffeeScript to JavaScript, Dart
to JavaScript and PHP to C++.
9
Throughout this book, I will use the general term,
compilation, rather than the specific term, transpilation. If I
do use the specific name you’ll understand the difference.
10
Language Features
This section will quickly introduce the key TypeScript
syntax used to supply explicit type annotations, classes and
interfaces. Although class-based object-orientation will be
familiar to most C# programmers, TypeScript is not based
on C#; it draws inspiration from many different languages.
This quick start is not intended to be exhaustive, but
supplies the information you’ll need to know before we
compare TypeScript to C#.
TypeScript is a statically typed language and will check type
compatibility at compile time. This improves the
programming experience by ensuring type-safety and by
supplying more relevant auto-completion at design time. If
you have used Visual Studio to edit JavaScript, you will have
noticed the auto-completion is not very intelligent. This is
because a variable in JavaScript does not have a fixed type.
In contrast, using TypeScript offers a highly productive auto-
complete system because it has intimate knowledge of types.
TypeScript Files
TypeScript files have a ‘.ts’ file extension. To add support for
TypeScript to Visual Studio 2012, you can download the
extension from:
http://www.typescriptlang.org/
11
You can also try most examples in the TypeScript
Playground:
http://www.typescriptlang.org/Playground/
Your program will be made up of a collection of TypeScript
files that contain your classes, grouped into modules.
Modules are similar to C# namespaces and can be used to
group related classes together. At runtime, your JavaScript
files can be added to web pages using standard script tags,
or loaded on-demand using a module loader, which will add
scripts to your web page as they are needed. When running
JavaScript on a server, NodeJS has a built-in module loader.
The method of referencing your dependencies differs based
on whether you are using a module loader. If you are not
using a module loader you specify your dependencies using
a reference comment:
12
Unlike the reference comment, the import statement is
converted into JavaScript code by the compiler. There are
two styles of modules that can be targeted – CommonJS
(RequireJS uses CommonJS) or AMD (NodeJS uses AMD).
You can switch between these using Visual Studio settings,
or by passing a module switch to the TypeScript compiler.
Here is an example of the compiled JavaScript for CommonJS
and AMD style modules:
} )
13
which means there is no network cost as there is when
sending JavaScript to a browser.
Types
TypeScript contains primitive types of string, number,
boolean, null and undefined. There is no distinction in
TypeScript between integers and floating-point numbers
because JavaScript does not define different numerical types
and it would not be efficient (or even possible in many
cases) for the TypeScript compiler to enforce integer values.
For situations where a dynamic type is desired, TypeScript
has a type named any. This is analogous to the C# dynamic
keyword.
It is possible to declare an array type by appending square
brackets to a type name, for example string[].
You can also define your own types, which I’ll return to in
the section on classes.
Type Inference
When you write JavaScript inside of a TypeScript file, the
compiler will automatically infer types in many scenarios.
Although the following example is plain JavaScript, you can
hover over the variable names in Visual Studio or the
TypeScript Playground to see the variables are typed
string, number and boolean respectively.
14
var myString = 'A String';
var myNumber = 1;
var myBoolean = true;
var example = {
name: 'Example',
id: 5,
collection: ['a', 'b', 'c']
}
When you type “example.” into the editor, you will get auto-
completion options of “name”, “id” and “collection” –
these are the only valid options for the example variable. If
you type “example.collection.” in the editor, you will
see options relevant to an array of string objects, because
TypeScript has worked out the type information.
Type inference does have its limitations. It can only infer the
type where the assignment is part of the variable
declaration, so if you declare a variable and assign it later in
your code (even on the next line) the type will not be
automatically inferred.
var unknown;
unknown = 'A String';
15
In this case, the unknown variable will have the special type
named “any”. A variable of this type is treated as a dynamic
type and type-safety is not guaranteed.
Type Annotations
In situations where the TypeScript compiler cannot infer the
types, or where you want to make your intentions explicit,
you can supply a type annotation. Unlike C#, where the type
appears before the variable name, the type annotation in
TypeScript appears after the variable name, and is separated
from the name by a colon. This style helps to indicate that
the types are optional.
Type annotations can be used to type variables, method
parameters, return values and interfaces.
Here are our example variables once again, but with the type
annotations to explicitly type them:
var example: {
name: string;
id: number;
collection: string[];
} = {
name: 'Example',
id: 5,
collection: ['a', 'b', 'c']
}
16
unknown = 'A String';
17
module Shapes {
export interface IPoint {
getDist(): number;
}
getDist() {
return Math.sqrt(this.x * this.x + this.y *
this.y);
}
module Animals {
export class Animal {
constructor(public animalName) { }
move() {
return this.animalName + ' the animal moves';
}
}
18
}
move() {
return this.animalName +
' the snake slithers';
}
}
move() {
return this.animalName +
' the cat cannot be bothered to move';
}
}
}
When you extend a class, you can call methods on the base
class using the super keyword. To access properties, you
use the this keyword. You are not forced to override any
methods on the base class, but if you have a constructor you
must call the constructor on the base class. If you forget to
do this the compiler will remind you with a compilation
error.
A module name can contain periods and you can extend
modules throughout your program. You need to balance the
value of long module names in TypeScript because there is
no concept of using statements as there is in C# to list your
19
dependencies, which means you need to use the full module
names in your code.
module Program.Utilities.Logging {
export class Console {
log(message: string): void {
if (typeof console !== 'undefined') {
console.log(message);
}
}
}
}
logger.log('Test message.');
Functions
Optional, Default and Rest Parameters
A method in TypeScript can contain optional, default and
rest parameters, which are all available in C# and work in
practically the same way. The syntax for declaring each of
these is shown below.
To mark a parameter as optional, append a question mark to
the parameter name. Optional parameters must be ordered
after non-optional parameters.
module Parameters {
export class Example {
optionalParameter(name?: string) {
return name;
20
}
}
}
module Parameters {
export class Example {
defaultParameter(name: string = 'Default') {
return name;
}
}
}
module Parameters {
export class Example {
restParameter(...name: string[]) {
return name.toString();
}
}
}
21
and any number of arguments, which are used to replace
tokens in this format string. The following example shows
the method signature for a TypeScript implementation of
string.Format:
module System {
export class String {
static format(formatString: string, ...args: any[]) {
// ...
}
}
}
22
findLastIndex(predicate: (T) => boolean,
index: number = this.length): number {
for (var i = index; i > -1; i--) {
if (predicate(this.list[i])) {
return i;
}
}
return -1;
}
23
List.prototype.findLastIndex = function (predicate, index)
{
if (typeof index === "undefined") {
index = this.length;
}
return -1;
};
Overloads
Overloaded methods in TypeScript differ from C# because
they only have a single implementation with multiple
signatures. In C# each signature would have its own
implementation. In TypeScript, the final signature
(sometimes called the “implementation signature”) is not
callable except via one of the overloads preceeding it. In the
example below, you can call the method with a string or a
number only – even though the implementation signature
accepts any type.
module Overloads {
24
export class Example {
overloadedMethod(input: string);
overloadedMethod(input: number);
overloadedMethod(input: any) {
return input.toString();
}
}
}
Enumerations
TypeScript enumerations are very similar to C#
enumerations. In both cases you can optionally allocate the
values to the enum.
enum States {
Solid,
Liquid,
Gas
}
enum Sizes {
Small = 3,
Medium = 5,
Large = 8
}
var States;
25
(function (States) {
States._map = [];
States._map[0] = "Solid";
States.Solid = 0;
States._map[1] = "Liquid";
States.Liquid = 1;
States._map[2] = "Gas";
States.Gas = 2;
})(States || (States = {}));
alert((<any>States)._map[state]); // "Liquid"
Generics
At the time of writing, generics have been newly added to
TypeScript, but in general they will be familiar to a C#
programmer – although they are styled after Java’s
implementation of generics.
module Generics {
interface IExample<T> {
getById(id: number): T;
26
}
class MyClass {
constructor() { }
}
Type Constraints
In C# you can add type constraints using the where
keyword, but in TypeScript the extends keyword is used
within the angle-brackets to specify a type constraint; this
has the same effect as C#’s where condition. In the example
below, the IExample interface accepts a type parameter only
if the type is a class that implements the IMyInterface
interface. The extends keyword in a type constraint is the
same whether you are ensuring a type implements an
interface or extends a class.
module GenericConstraints {
export interface IMyInterface{
name: string;
}
constructor() {
this.name = 'A string';
}
}
28
Structural Typing
C# has a nominative type system, whereby the type of an
object is determined using explicit declarations. If two types
had identical structures, they would not be considered the
same type. TypeScript is structurally typed; any types with
compatible structures are considered the same type
Here is an example in C#. ClassA and ClassB are not
compatible types. To make these types compatible they
would need explicit inheritance or an interface to describe
their commonality.
class ClassA {
SomeMethod(name: string) {
return name;
}
}
class ClassB {
SomeMethod(name: string) {
return 'Arbitrary value';
}
}
30
var objA = { firstName: 'Steve', lastName: 'Fenton' };
var objB = { firstName: 'Mark', lastName: 'Jones' };
var objC = { firstName: 'James', lastName: 'Llang' };
{
firstName: string;
lastName: string;
}
31
var objA = { firstName: 'Steve', lastName: 'Fenton' };
var objB = { firstName: 'Nick' };
// Not allowed
//objD.lastName = 'Llang';
32
Access Modifiers
TypeScript has a small set of access modifiers that let you
hide implementation details from the rest of your
TypeScript program. It is important to note this is a compile-
time feature that doesn’t prevent access at runtime. When
you write a module, everything inside the module is private
unless you explicitly make it available using the export
keyword. If you omit the export keyword, you can only
create and use instances within the module. If you try to
return an instance of a private class from a public method,
the compiler will issue an error.
Inside a class, things are the other way around; methods and
properties are public by default, so you need to explicitly
hide them with the private modifier. You can optionally
use the public modifier to make your intentions explicit if
you wish.
module AccessModifiers {
// A private class (no export keyword)
class PrivateClass {
//...
}
// A public class
export class PublicClass {
private privateProperty: string = 'Private';
publicProperty: string = 'Public';
implicitPublicMethod() {
return 'Public';
33
}
public explicitPublicMethod() {
return 'Public';
}
private privateMethod() {
return 'Private';
}
}
}
alert(publicClass.publicProperty);
// Not allowed
//alert(publicClass.privateProperty);
// Not allowed
//var privateClass = new AccessModifiers.PrivateClass();
module InheritanceAccess {
class BaseClass {
publicProperty = 'Public';
private privateProperty = 'Private';
}
// Not allowed
//super.privateProperty = 'Private';
}
34
}
}
35
Memory Management
When you run a TypeScript program, it is the compiled
JavaScript being executed. JavaScript behaves similarly to
.NET in terms of memory management; memory is allocated
when objects are created and freed when the objects are not
used any more. The major difference in the JavaScript world
is each browser or server technology may have a different
implementation of a garbage collector, which means the
memory profile of your program may be less predictable
than it would be if you wrote it in C#.
For example, older web browsers may use a reference-
counting garbage collector, freeing memory when the
number of references to an object reaches zero. This is a
very fast way to collect garbage as the memory can be freed
as soon as the reference count reaches zero. However, if a
circular reference is created between two or more objects,
none of these objects will ever be garbage collected because
their count will never reach zero.
This problem is largely solved in modern web browsers,
which use a mark-and-sweep algorithm to detect all
reachable objects and garbage-collect the objects that are
not reachable. Although this style of garbage collection can
take longer, it is less likely to result in memory leaks.
36
Reference Counting
Object Reference Count
Object 1 1
Object 2 3
Object 3 0
37
Mark-and-Sweep
38
Releasing Resources
In TypeScript, you are more likely to encounter unmanaged
resources when using Node.js than when running inside of a
browser. Most browser APIs that interact with hardware are
designed to accept a method you supply as a callback, so you
never hold a reference to the unmanaged resource in your
program. For example, if you wanted to use the proximity
API, which detects when an object is near the sensor, you
would use the following code:
window.addEventListener('userproximity',
sensorChange, true);
39
var file = File.open();
try {
file.write('Example');
} finally {
file.close();
}
40
Exceptions
You can raise an exception in TypeScript using the throw
keyword. In JavaScript you can follow the throw statement
with any type, although typically strings are used to supply
an error message. In TypeScript it is more usual to throw an
Error object.
41
blocks. Instead, you can test the exception name inside your
catch block.
try {
if (true) {
throw (new ExampleError('An error has occurred.'));
}
} catch (error) {
switch (error.name) {
case 'ExampleError':
alert(error.message);
break;
default:
throw error;
}
}
42
Try-Catch-Finally Blocks
Try block
N
Finally block
43
Arrays
From TypeScript 0.9 onwards, arrays are generic and have
precise typing for their contents. To take advantage of this,
you can supply a type annotation with square brackets
appended. TypeScript checks the types of items being added
to the array and will infer the type for items retrieved from
the array.
class MyType {
constructor(public name: string) {
}
}
44
class List<T> {
private list: T[] = [];
constructor(list?: T[]) {
this.list = list || [];
}
clear() {
this.list = [];
}
getItem(index: number): T {
if (this.list[index]) {
return this.list[index];
}
}
toArray(): T[] {
var arr: T[] = [];
for (var i = 0; i < this.list.length; i++) {
arr.push(this.list[i]);
}
return arr;
}
47
alert(list.length.toString()); // 2
alert(list.getItem(1)); // 'Two'
list.addRange(['Three', 'Four']);
alert(list.length.toString()); // 4
48
Dates
Dates in TypeScript are based on the JavaScript Date object,
which are represented by the number of milliseconds since
1 January 1970 00:00:00 UTC.
The Date object accepts arguments from least to greatest
precision.
49
Now
You can access the current date and time using the
Date.now() method. As this returns the value in
milliseconds, you will need to pass this value into a new
Date object if you want to perform date operations.
Date Methods
Once you have a Date object, you can access the individual
date components using the methods built into the Date
object. There are two sets of methods; one gives access to
the date components in local time, the other returns the UTC
values of the date components. In the example below, you’ll
note we add one to the month, because the value returned
from getMonth and getUTCMonth is zero-based, so January
is 0, February is 1 and so on.
50
The date components are available for year, month, day-of-
month (date), hours, minutes, seconds and milliseconds. All
of these values are available in local and UTC flavours.
You can also get the day of the week using getDay or
getUTCDay, which again is zero based, with Sunday being 0,
Monday being 1 and so on.
You can also take advantage of methods that display the
Date object in various formats. The following examples are
based on a date of 3rd June 2013 at 9:25pm UTC. The locale
strings are based on the user’s browser, or the server
running Node.js.
alert(now.toDateString());
// Mon Jun 03 2013
alert(now.toISOString());
// 2013-06-03T21:25:05.531Z
alert(now.toJSON());
// 2013-06-03T21:25:05.531Z
alert(now.toLocaleDateString());
// 03 June 2013
alert(now.toLocaleString());
// 03 June 2013 22:25:05
alert(now.toLocaleTimeString());
// 22:25:05
51
Events
Events in TypeScript come from the Document Object Model
API, which has a standard set of built in events for mouse
and keyboard interactions and for object and form events.
The following list of events is not exhaustive, but supplies
some indication of the kind of built-in events you can
subscribe to.
Mouse Events
Event Triggered when…
52
The mouse pointer is moved over the
onmouseover
target element.
Keyboard Events
Event Triggered when…
Object Events
Event Triggered when…
53
onunload A document is closed.
Form Events
Event Triggered when…
Custom Events
The same mechanism can be used to dispatch and listen for
custom events in your TypeScript program. Any given event
can have multiple listeners and multiple dispatchers.
Here is an example of a class that listens for a custom event:
class ListenerOne {
constructor () {
document.addEventListener('myCustomEvent',
this.eventListener);
}
eventListener(e: Event) {
alert('Event caught by ListenerOne');
}
54
}
Running Order
Events run in the order they are registered. This is an
important consideration because event handlers run in
sequence, not concurrently.
If you register two event listeners and the first one takes five
seconds to run, the second event handler will not be called
until the first has completed. If the first fails, the second
event handler will still be called.
You can prevent an event listener from blocking other event
listeners by calling long-running actions in a zero-
millisecond timeout.
55
eventListener(e: Event) {
window.setTimeout(longRunningMethod, 0);
}
56
Framework
TypeScript comes bundled with definitions for the objects
and methods you would expect to find in a browser. Because
web standards are constantly evolving you may discover the
need to extend these definitions to include something new
that hasn’t made it into the standard library. You can view
the standard library file on your computer by browsing to
the TypeScript SDK in your Program Files directory, for
example:
C:\Program Files (x86)\Microsoft
SDKs\TypeScript\lib.d.ts.
You should never edit this file Most of the library definitions
use interfaces to declare the types and TypeScript interfaces
are open, which means you can continue their definition in
multiple files.
For example, the following is the current definition of
ClientRectList in the TypeScript library file.
interface ClientRectList {
length: number;
item(index: number): ClientRect;
[index: number]: ClientRect;
}
57
If a new property named isOrdered was added to the
specification of ClientRectList and you needed to use
this property, you could simply add the following interface
extension to your program to make it available immediately.
interface ClientRectList {
isOrdered: boolean;
}
59
Creating Definitions
There are occasions where you will want to consume a
JavaScript library, framework or toolkit in your TypeScript
program. Without some help, the TypeScript compiler will
struggle to understand this external code, for which it has no
type information.
Dynamic Declarations
The simplest way to use external code in your program is to
declare a variable with the any keyword. The TypeScript
compiler will now allow any method or property calls
against this dynamic object. This takes you past the
compiler but doesn’t give you rich auto-completion or type
checking.
$.anythingYouLike();
60
Type Declarations
To get a better development experience with the external
code, you can supply a more comprehensive declaration.
You can declare variables, modules, classes and functions in
order to define the type information for the external code.
You may start by declaring the parts you intend to use
immediately and extend the declarations on an as-needed
basis. The declare keyword only needs to appear at the
start of the declaration.
interface CannotBeExtended {
run(): string;
}
61
declare var lib2: CannotBeExtended;
interface AmazingMove {
(x: number, y: number): void;
up(distance: number): void;
down(distance: number): void;
left(distance: number): void;
right(distance: number): void;
}
interface Amazing {
move: AmazingMove;
}
amazing.move(40, 30);
amazing.move.up(40);
63
Useful Tricks
Obtaining Runtime Types
It can be useful to inspect the class name at runtime. In C#
there are several ways to obtain the type of an object, but in
TypeScript it is not so obvious.
class Types {
static getType(inputClass) {
var funcNameRegex = /function (.{1,})\(/;
var results = (funcNameRegex).exec((<any>
inputClass).constructor.toString());
return (results && results.length > 1) ? results[1]
: '';
}
}
class Example {
}
alert(Types.getType(x)); // Example
alert(Types.getType(y)); // AnotherClass
64
The limitation on this technique is you cannot obtain the full
module path to the class, which means MyModule.Example,
SomeOtherModule.Example and the unwrapped class
Example all return the string “Example”.
document.querySelectorAll('.myClass').onclick = function ()
{
alert(this.innerHTML);
};
65
have an onclick function. To solve this type checking issue,
you can simply extend the NodeList interface.
interface NodeList {
onclick: (handler: Function) => void;
}
66
67