C# String extensions to TypeScript prototype extensions

Extending a base type with additional methods

ยท

2 min read

Microsoft C# extension methods is a great way to "add" methods to existing types without creating a new derived type, recompiling, or otherwise modifying the original type.

My requirements

I needed two extension methods to do the following:

  • A method to convert "Yes"/"No" to it's equivalent boolean value
  • A method to convert a date string to a Date object (or the minimum Date if it's a blank or malformed date string)

My C# class

So I wrote my C# extension methods as follows:

public static class StringExtensions {
    public static bool ToBool(this string value){
        return value.ToLower() == "yes";
    }

    public static DateTime ToDate(this string value){
        return DateTime.TryParse(value, out DateTime result) 
                    ? result : DateTime.MinValue;
    }    
}

You can see a working .NET Fiddle below:

Equivalent TypeScript extensions

So I wrote the equivalent extensions in the TypeScript playground

...
String.prototype.toBoolean = function () {
    return this.toLowerCase() === "yes";
};

String.prototype.toDate = function () {
    return this != "" ? new Date(Date.parse(this.toString())) : 
        new Date("01-Jan-2001");
}
...

Interesting, this code gives a compile time error. But still runs ๐Ÿ˜ฑ

Property 'toBoolean' does not exist on type 'String'.

Property 'toDate' does not exist on type 'String'.

How did I fix it?

I needed to declare an interface for String first as you can see in my playground script and the code below:

interface String {
    toBoolean(): boolean;
    toDate(): Date | undefined;
}

String.prototype.toBoolean = function () {
    return this.toLowerCase() === "yes";
};

String.prototype.toDate = function () {
    return this != "" ? new Date(Date.parse(this.toString())) : 
        new Date("01-Jan-2001");
}
...

This TypeScript code compiled and ran without any issues. Note that when you do this in your local environment, add the interface code to a String.d.ts file to keep it neat.

This SO Answer helped me to write the code.

Why did it work?

This SO Answer gives the explanation as to why adding an interface works:

Declaring the new member so it can pass type-checking. You need to declare an interface with the same name as the constructor/class you want to modify and put it under the correct declared namespace/module. This is called scope augmentation.

ย