Photo by Pixabay from Pexels - My own edit
This article will teach you how to add custom methods and properties
classes using Prototype & Class-based inheritance and C# Extension Methods.
We are going to create three custom methods that can be applied
to any type of collection, array, set, or map:
This method will lookup the length/size of the collection and
return true or false if the collection is empty or not
This method will push a new item into a collection and return
an existing collection
This method will take a callback function (from console.log to custom function)
that can do pretty much anything and return an existing
This is going to work similarly to
Extension Methods in C#. Any consumer of the collection will be able to use
these methods as well as the default ones.
The downside of the original implementation is that different collection types use
different methods to interact with it. The things we're interested in are as follows:
Push new item into collection (array: push(), set: add(), map: set())
Inspect the size of the collection (array: .length, set, map: .size)
So instead of needing to remember which property applies to which collection,
we'll create custom unified methods that work for either.
Another important detail is the return type. If our custom method returns
an existing collection it means that we can append other methods from that collection to
But if the method returns a different value,
we can no longer append a collection method to it.
This is super easy to implement, and we'll see what it looks like
with different paradigms, such as Prototype and Class-based inheritance,
and in different programming languages.
Photo by WallpaperCave
every object has an internal property called its prototype, which is a reference to another
object that serves as its prototype.
The prototype is like a tree where all properties and methods on a certain object exist.
For example, if we create an array, we can expand the prototype property to see all
and properties available on the arrays.
Array prototype preview
To add a new method to this prototype we'll do something like this:
The important thing to notice is that we're using a function statement as opposed to
an array function. But why? - It's because we'll use this
keyword within the function and this will refer to the consumer of the
We are going to use the same formula as above to extend each collection type
(array, set, map) with each of our custom methods (isEmpty, insert, tap).
This method will check the length/size of the collection, and tell us
whether it's empty or not.
This method will push a new item into a collection.
This is the part where I said that we'll have a unified solution. Because as we
arrays use push() to append a new item
sets use add() to append a new item
maps use set() to append a new item
and now all three can use insert() to do the same thing for each.
The tap method will look through each element within an array and call our callback
This callback function can be an action dispatcher function, console.log/warn/error,
or any custom function.
Now let's test each method. Since we extended the original prototype,
we can call our new methods directly on the instances.
The basic premise will look like this. Our custom class will extend the base (Super)
Upon extending such classes it's also important to call super() within
of a derived (Sub) class. This is necessary because a derived class inherits all of
the properties and methods of its parent class, and the parent class may have its own
constructor logic that needs to be executed in addition to the constructor logic of the
We're going to repeat this process for each of our target classes:
# Extended Array
# Extended Set
# Extended Map
To test this we'll need to create a new instance of each of our custom classes
as opposed to working with core classes (which we did previously).
We'll push a new item into a collection using our insert method,
inspect the collection using the tap method and then check if the collection is
using the isEmpty method.
And it worked. Now we can use these newly created methods whenever we're working
with these types of collections.
Let's learn how to put this together in TypeScript as well.
Image from WallpaperAccess.com
To apply similar class-based logic in the world of TypeScript we need to
add generic types to TypeScript classes.
# Extended Array
# Extended Set
# Extended Map
To reassure that this once again works we're going to test it the same way.
Prototype-Based Inheritance TypeScript
In order for Prototypes to work with TypeScript we need to
add additional functionalities. Here is why.
the TypeScript compiler will complain that the isEmpty method
does not exist on the type of array.
To overcome this issue we need to create a type declaration file (d.ts)
where we'll specify rules for the types we'll use.
Basically, we'll create the isEmpty method for each type.
We mentioned C# on several occasions in the article so we might as well add some input
we'll make use of C# Extension methods. The core idea is similar,
we create a custom class with a custom method (extension) that will do the job we tell
A few things to keep in mind:
class and method must be static
the first parameter of the extension method specifies the type that
it is going to be operated on. This is the consumer of the extension.
the first parameter is preceded by this keyword. This is what
tells C# that this method is an extension to the provided type (in this case
With this in place, we import our Utils class in the file
where we wish to use the extension and apply it;
Now let's apply this pattern to our three custom extensions.
It's important to note here that, C# also supports class-based inheritance, as we did
We'll start by creating an extension method.
The beauty of C# collections, is that they all implement the
interface. You've might have encountered something like this before.
If we create a method that returns IEnumarable, it won't really matter whether the method
compiler won't complain.
KeyValuePair<K, V>, because IEnumerable excepts a single (wrapper) type.
Back to our example. The isEmpty method will work with collections, so the parameter
to our extension method will be of type IEnumerable<T>.
We then call .Any() method on the desired collection.
This returns true or false whether the collection is empty or not.
Let's put this to the test:
Passed with flying colors.
For this method - we're going to use Stack and List classes.
Here, we won't be using IEnumerable, as we cannot insert data into IEnumerable.
Instead, we'll use create and override a method that can work with List<T> and
The consumer of either class can now use the insert method,
without knowing the underlying implementation.
To create a callback function in C#, we'll make use of the
Here we'll use a List and HashSet (that is equivalent to a Set collection in
Now let's test this:
This can be applied to dictionaries too:
Photo by Skitterphoto from Pexels
The beauty of making extensions
If I have not convinced you to use extensions thus far and this should put a pin to it.
Using the methods we created above we'll create an empty array and add items to it.
Now let's swap an Array class with a Set class.
And everything works as expected with zero code changes.
The difference in the output is that the Set collection returns only unique elements.
Photo by Monstera from Pexels
The difficulties of working with extensions
Let's talk about potential issues with creating custom extensions:
The first thing to be aware of is that we need to have all consumers of the extension
be on board with the extension method they're calling. For example, if we're using
List<T> in C# and I want to combine it with Stack<T>, we can only use
within extensions that are available in both.
And if the two do not share a common language then we can implement one method for each
class<T> as we did above for the insert method.
Future language changes.
If we add a custom method in code and the same method is introduced by the
core language team (JS/C#) in the feature, it may create ambiguity that leads to broken
Another danger of creating custom prototypes is that we can break code that relies on
the default behavior of built-in objects. For example, if we modify the
String.prototype object to add a custom method,
any code that relies on the default behavior of
the String object could be affected and potentially break.
Similarly with the Extension Methods in C#. If the name of the extension method
conflicts with an existing property, it could cause unexpected behavior or errors.
Extensions are a fun way to introduce new features, abstract implementation,
and create unified solutions, but may cause issues if used recklessly.