Types in C# and Java
You may be familiar with the types in managed-type languages like Java or C#. In these languages, we have two kinds of types.
primitive types
- These types are value-based
- These types are predefined by the language
- Examples are
int
,string
,float
,bool
,byte
,double
,- In c#, you can use
typeof
operator to get the type of a type andGetType()
method to get the type of a variable.
Console.WriteLine(typeof(int)); // System.Int32
Console.WriteLine(typeof(byte)); // System.Byte
Console.WriteLine(typeof(double)); // System.Double
Console.WriteLine(typeof(string)); // System.String
var str = "string";
Console.WriteLine(str.GetType()); // System.String
- In java, you can use
instanceof
operator to check if an object is a type or not
Object d = 0.0;
System.out.println(d instanceof Double);//true
Non-primitive types
- These types are generally referenced based (except Enums and struct in c#)
- These types are user-defined types and users can define and create them using class, enum, struct, interface, delegate, etc.
- In c# you can use
typeof
operator to get the type of a Type andGetType()
method to get the type of a variable.
class Program {
public static void Main() {
Console.WriteLine(typeof(Program));//Program
var program = new Program();
Console.WriteLine(program.GetType());//Program
}
}
- In Java, you can use
instanceof
operator.
class Car{
interface IExample {
public void print();
}
public static void main(String args[]){
Car c=new Car();
System.out.println(c instanceof Car);//true
Object obj = new IExample() {
public void print() {
}
};
System.out.println(obj instanceof IExample);//true
}
}
Types in typescript
primitive types
- Typescript's primitive types are
string
,number
,boolean
,bigint
,undefined
- These types are value-based
- These types are predefined by the language
- You can use
typeof
operator to get the types of values.
let big1 = 9007199254740991n;
console.log(typeof(big1)) // "bigint"
let bool = true
console.log(typeof bool) // "boolean"
let undef
console.log(typeof undef)//"undefined"
- You can consider primitive types in Typescript to work similarly to those in Java and C#, but actually they do not! Javascript types have more to offer. You will see in a moment.
Non-primitive types
- These types are generally referenced based (except Enums)
- These types are user-defined types and users can define and create them using class, type, interface, function, etc.
- Do not confuse the general word type which is a programming concept with Typescript's
type
keyword which is one way of creating types.- You can use
typeof
operator to get the types of values.
type Obj = { x: string }
const obj: Obj = { x: "" }
console.log(typeof obj) // "object"
class Class { constructor(public a: number){} }
const cls: Class = new Class(1)
console.log(typeof cls)// "object"
interface Interface { i: number }
const obj2: Interface = { i: 1 }
console.log(typeof obj2)// "object"
let nl = null
console.log(typeof nl)// "object"
- As you may have noticed, when it comes to the
typeof
operator in TypeScript, the results for non-primitive types are all "object". Here, it's important to adjust our understanding of types in TypeScript. In Typescript Instead of thinking of a type as a rigid definition, consider it as a set (Unique values) that encompasses multiple values. This characteristic makes TypeScript types incredibly flexible and powerful. In languages like C# and Java, a variable can only have a single type, and you can create new types by utilizing techniques like inheritance or interface implementation. However, in TypeScript, you not only have access to those same techniques, but you also have a wide range of additional methods to manipulate types like combining them, filtering a type to create a new type, map an existing type to another one and so on. Below I introduce the patterns that you can use to work with types in Typescript and see how they are different from managed-type languages.
Unions of primitive types / values
In languages like C# and java, a variable can only be one type at the same time. For example either a string or number and so on. In Typescript you can use the union of multiple types as a new type or union of multiple values as a new type.
//union of two primitive types
type stringOrNumber = string | number;
let son: stringOrNumber = "text"
console.log(typeof son) // "string"
console.log(son) // "text"
son = 1
console.log(typeof son) //number
console.log(son) // 1
//union of multiple number values
type DiceNumber = 1 | 2 | 3 | 4 | 5 | 6;
let diceNumber: DiceNumber = 1
//let diceNumber: DiceNumber = 10 // error: Type '10' is not assignable to type 'DiceNumber'
console.log(typeof diceNumber) // "number"
console.log(diceNumber) // 1
//template union types
type Langs = 'en' | 'fr'
type texts = 'footer' | 'header'
type combine = `${Langs}_${texts}`
/*
type combine = "en_footer" | "en_header" | "fr_footer" | "fr_header"
*/
Extend object
types
You can extend type, class or interface types to create new types. For classes and interfaces, aside from minor differences, it is what you are also doing in languages like C# or Java.
type Person = { name: string; age: number;};
//Create a new type by removing the name
type PersonWithoutName = Omit<Person,'name'>
//create new type by adding id to Person type
type PersonWithId = {id: string; } & Person;
//create a new type using intersection of two other types
type Dim1 = {x: number}
type Dim2 = {y: number, z: number}
type Dim3 = Dim1 & Dim2
interface IPersion {name: string}
//create new type by extending IPersion
interface IPersopnWithId extends IPersion { id: number;}
// Add properties to an existing interface by redeclaring the interface name. (You cannot do this for `type`)
interface IPersion {age: number}
class Parent {
constructor(public a: number) {}
}
// Create a new type by extending Parent class
class Child extends Parent {
constructor(public b: number, public a: number)
{
super(a);
}
}
Map an existing type to another by applying a mapper type
You can create a new type from an existing type by changing the properties of the old one using a mapper.
type Artist = {
id: number;
name: string;
bio: string;
}
// a mapper that makes all fileds of the old type optional and add a mandatory id filed
type MyPartialTypeForEdit<Type> = {
[Property in keyof Type]?: Type[Property];
} & { id: number };
// this type contains all fileds from Artist type but optional
type ArtistOptionalFields = MyPartialTypeForEdit<Artist>;
/*
type ArtistOptionalFields = {
id?: number | undefined;
name?: string | undefined;
bio?: string | undefined;
} & {
id: number;
}
*/
Filter types by applying a conditional type
You can create a new type by setting some rules. Then apply this conditional type to a compound type to filter it.
// If A extends this type-> {legs: 4}, then A has four legs, otherwise never.
type HasFourLegs<A> = A extends {legs: 4} ? A : never
type Bird = { legs: 2, wings: number };
type Dog = { legs: 4, breed: string};
type Ant = { legs: 12};
type Lion = { legs: 4};
type Animals = Bird | Dog | Ant | Lion
type four_legs = HasFourLegs<Animals> // Dog | Lion
Mix classes using mixins
In typescript, like most other programming languages, a class can only inherit from one parent class. But what if we want to have the capabilities of multiple classes in one class (just like C++). Do not worry. Typescript allows you create a new type by mixing classes.
class Jumpable { jump() { console.log("jump");} }
class Duckable { duck() {console.log("duck");} }
//Our mixed class
class Sprite { x = 0; y = 0; }
// Then you create an interface which merges
// the expected mixins with the same name as your base
interface Sprite extends Jumpable, Duckable {}
// Apply the mixins into the base class via the JS at runtime
applyMixins(Sprite, [Jumpable, Duckable]);
let player = new Sprite();
player.jump();
player.duck();
// This can live anywhere in your codebase:
function applyMixins(derivedCtor: any, constructors: any[]) {
constructors.forEach((baseCtor) => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
Object.defineProperty(
derivedCtor.prototype,
name,
Object.getOwnPropertyDescriptor(baseCtor.prototype, name) ||
Object.create(null)
);
});
});
}
Picking properties from another type
You can use Pick<>
to pick certain properties from another type and create a new type.
Type User = {
id: string,
name: string,
age: string,
email: string,
phone: number,
password: string
}
Type LoginUser = Pick<User,'email' | 'password'>
What else you can add to this list?
Top comments (1)
Thank you! There were many great points about TypeScript, especially when you're using examples from other languages.