Can we use reflection in Type Script just like C# to get the list of classes which implement a certain base class?
For example, let say Snake and Horse implement the
If you are willing to take a dependency on a feature which is likely to change fairly soon, as it is based on an obsolete specification, and if you are willing to use only classes and not interfaces, you can accomplish this using a decorator.
Here is an example:
hierarchy-tracked.ts
export default function hierarchyTracked(target: new (...args: any[]) => object) {
for (const proto of walkPrototypeChain(target)) {
if (!Object.hasOwnProperty.call(proto, 'extendedBy')) {
const extendedBy: typeof Function.extendedBy = [];
Object.defineProperty(proto, 'extendedBy', {
get: () => extendedBy
});
}
// ! is used to suppress a strictNullChecks error on optional.
// This is OK since we know it is now defined.
proto.extendedBy!.push(target);
}
}
declare global {
interface Function {
// Declared as optional because not all classes are extended.
extendedBy?: Array object>;
}
}
function* walkPrototypeChain(target: new (...args: any[]) => object) {
let proto = Reflect.getPrototypeOf(target);
while (proto && proto !== Object) {
yield proto;
proto = Reflect.getPrototypeOf(proto);
}
}
animals.ts
import hierarchyTracked from './hierarachy-tracked';
export class Animal {
alive = true;
static get slayable() {return true;}
static extinct = false;
}
@hierarchyTracked export class Snake extends Animal {
isEctotherm = true;
}
@hierarchyTracked export class Cobra extends Snake {
isDeadly = true;
}
@hierarchyTracked export class Horse extends Animal {
isEndotherm = true;
}
// logs
Animal.extendedBy && Animal.extendedBy.map(Taxon => Taxon.name)
.forEach(name => {
console.log(name);
});
// Snake
// Cobra
// Horse
Snake.extendedBy && Snake.extendedBy.map(Taxon => Taxon.name)
.forEach(name => {
console.log(name);
});
// Cobra
There is no need to resort to global state and it is actually quite tidy and explicit.
This also works with Babel 7 if you are not using TypeScript. (note that the same caveats regarding decorator usage mentioned above still apply)
Of course this is trivial to write manually if you do not want to rely on decorators:
import trackHierarchy from './hierarachy-tracked';
export class Animal { }
class Snake extends Animal { ... }
trackHierarchy(Snake);
export {Snake};
Back to your example code above, it is easily achieved.
It goes from
var childTypes = assembly.GetTypes().Where(_ => _.IsSubclassOf(typeof(Animal)));
to simply
const childClasses = Animal.extendedBy || [];
A Word of Warning
If you find yourself wanting to write code like this, you should take a step back and make sure you actually know JavaScript. This sort of pattern, and indeed your example use case, usually indicates that someone has come to the language with a classical mindset, noticed ES 2015 classes, and began to think they are related to classes in traditional languages.
ES classes could not be less like C#, C++, Java, or Scala classes.
First and foremost: Classes in JavaScript are not types.
Classes in JavaScript are values.
Their declaration form is fundamentally just a syntactic sugar over prototypes. The pattern you are trying to achieve suggests you may not understand this well. In particular, it suggests that you may think that they are special.