Chris Tartamella
Chris Tartamella

Reputation: 45

Implementing a singleton in a generic Typescript class

I have a generic type that will be the base class for events in an Event system. We expect these to be singletons but it also is generic so that subclasses can specify the type of the argument they expect in the callback when the event fires.

What I have looks something like this:

export interface IEvent { }
export interfact IEventArg { }

export abstract class Event<TEvent extends IEvent, TEventArg extends IEventArg> implements IMessageHandler {

private static s_instance : any;
private static s_isInitializing : boolean;

constructor() {
    if (!Event.s_isInitializing) 
        throw new Error ("Use the GetInstance method to get this class instance.");
}

public static GetInstance<TEvent extends IEvent, TEventArg extends IEventArg>(type: { new() : T; }) : TEvent {
    if (!s_instance) {
        s_isInitializing = true;
        s_instance = new type();
        s_isInitializing = false;
    }

    return s_instance;
}
}

export class SelectionEvent extends Event<SelectionEvent, EmptyEventArg> {
...
}

var event = SelectionEvent.GetInstance(SelectionEvent);

There are some things in here I consider odd and are there mostly because of Typescript language requirements. The argument to GetInstance seems to be necessary because var x = new TEvent() seems to not be allowed by TypeScript. The empty interfaces help enforce what can be accepted as generic types.

What does not seem to work here is the combination of static variables and generic types. The block within the GetInstance method is what i WANT to do but obviously it does not compile. I'm also concerned that the static instance variable will not be created once per Event but rather just once per Event instance and will thus be overwritten.

Any guidance here would be much appreciated.

Upvotes: 3

Views: 6303

Answers (1)

Ryan Cavanaugh
Ryan Cavanaugh

Reputation: 221192

In C#, each instantiation (set of type arguments) of a generic class gets its own set of static members. This means you can write a singleton pattern where you have one instance for Foo<int>, another for Foo<string>, and so on. This is possible because generics are manifest at runtime -- the runtime system actually knows what generics are and can allocate new objects for each type instantiation. Additionally, the static side of a class can reference the type parameters of that class (this is a consequence of the runtime semantics).

In TypeScript, this is not the case. There is only one constructor function per class, generic or otherwise. This means that the static side of the class cannot "see" the generic type parameters, because for any class X<T> with a static member y, there is only one runtime slot for X.y.

The singleton pattern for classes is generally a poor fit for TypeScript; see How to define Singleton in TypeScript . I think you have some XY problem going on here so I can't recommend any concrete alternative without more information on what your use case is (maybe post a separate question).

Also, you should never have empty interfaces in TypeScript. Types are compared structurally, so any two empty types are compatible, and you can assign anything to a variable typed as an empty interface.

Upvotes: 5

Related Questions