Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Interface Contravariance in TypeScript

As an intellectual exercise, I thought I'd see how closely I could implement some of the .Net generics members in TypeScript (0.9.5), I've gotten as far as List<T> but not sure I can progress.

(I realise there are workarounds for this, but I'm specifically trying to use the same implementation as exists in .Net libs, mostly to try and get an idea of the limitations still present in TypeScript).

Anyway, ignoring the fact that I don't seem to be able to overload my constructors in any meaningful way, in the .Net source the constructor List(IEnumerable<T> collection) checks that the passed Enumerable is not null, and then contravariantly casts it to an ICollection using ICollection<T> c = collection as ICollection<T>.

In TypeScript I do this var c: ICollection<T> = collection (collection is an IEnumerable<T>), but get the following Error:

Cannot convert 'IEnumerable<T>' to 'ICollection<T>': Type 'IEnumerable<T>' is missing property 'Add' from type 'ICollection<T>'.

My current code is as follows:

export module System {

    export module Collections {

        export interface IEnumerator {
            MoveNext(): boolean;
            Reset(): void;
            Current(): any;
        }
        export interface IEnumerable {
            GetEnumerator(): IEnumerator;
        }
        export interface ICollection extends IEnumerable {
            CopyTo(array: any[], index: number): void;
            Count(): number;
            SyncRoot(): any;
            IsSynchronized(): boolean;
        }
        export interface IList extends ICollection {
            [index: number]: any;
            Add(value: any): number;
            Contains(value: any): boolean;
            Clear(): void;
            IsReadOnly: boolean;
            IsFixedSize: boolean;
            IndexOf(value: any): number;
            Insert(index: number, value: any): void;
            Remove(value: any): void;
            RemoveAt(index: number): void;
        }

        export module Generic {

            export interface IEnumerator<T> extends System.Collections.IEnumerator {
                Current(): T;
            }
            export interface IEnumerable<T> extends System.Collections.IEnumerable {
                GetEnumerator(): IEnumerator<T>;
            }
            export interface ICollection<T> extends IEnumerable<T> {
                Add(item: T): void;
                Clear(): void;
                Contains(item: T): boolean;
                CopyTo(array: T[], arrayIndex: number): void;
                Remove(item: T): boolean;
                Count(): number;
                IsReadOnly(); boolean;
            }
            export interface IList<T> extends ICollection<T> {
                IndexOf(item: T): number;
                Insert(index: number, item: T): void;
                RemoveAt(index: number): void;
                [index: number]: T;
            }
            export interface IReadOnlyCollection<T> extends IEnumerable<T> {
                Count(): number;
            }
            export interface IReadOnlyList<T> extends IReadOnlyCollection<T> {
                [index: number]: T;
            }

            export class List<T> implements IList<T>, System.Collections.IList, IReadOnlyList<T> {
                private _defaultCapacity: number = 4;

                private _items: T[];
                private _size: number;
                private _version: number;
                private _syncRoot: any;

                constructor(collection?: IEnumerable<T>, capacity?: number) {
                    // NOTE: Capacity will be ignored is Collection is not null
                    //       This is because we don't appear to be able to overload ctors in TypeScript yet!
                    if (collection == null) {
                        if (capacity == null) {
                            this._items = new Array<T>(0);
                        }
                        else {
                            this._items = new Array<T>(capacity);
                        }
                    } else {
                        var c: ICollection<T> = collection;

                    }
                }
            }
        }
    }
}

Has anyone else tried co/contra-variance with interfaces? If so, how did you get on?

Thanks,

like image 696
Tom Tregenna Avatar asked Mar 21 '23 19:03

Tom Tregenna


1 Answers

You can suppress the warning using a type assertion in TypeScript:

var c: ICollection<T> = <ICollection<T>> collection;

Type assertions don't actually cast the value - at runtime none of these type annotations or assertions exist as they are all removed. The runtime value is entirely unaffected by a type assertion.

But this doesn't solve the problem that you won't have an add method.

Also, null is an unlikely value for your test here:

if (collection == null) {

null is usually only used for deliberately absent values, you are much more likely to encounter undefined, for example. You can test for both of these using the short hand:

if (!collection) {

I know that you are performing an intellectual activity, so this next comment absolutely doesn't apply - but for anyone else it is worth noting that recreating things like List and Collection method-for-method to replicate .NET misses the point that TypeScript (and JavaScript) is a different language. All arrays in your TypeScript program are generic, so you get little benefit from wrapping them in all these different ways - it all just becomes overhead. But as I mentioned, as an intellectual exercise this is interesting stuff.

like image 80
Fenton Avatar answered Mar 29 '23 20:03

Fenton