Typescript: Define properties like in C#

Created on 22 Mar 2016  ·  11Comments  ·  Source: microsoft/TypeScript

When you're creating a class and you want to define some properties you have to write much code to do so.
Current way

class MyClass {

    /**
     * Creates a new Instance of this class
     */
    constructor(private _id: number, private _name: string) {
    }

    /**
     * To define a property, you need a get function ...
     */
    get id(): number {
        return this._id;
    }

    /**
     * ... and a set function ...
     */
    set id(value: number) {
        this._id= value;
    }

    /**
     * ... for every single ...
     */
    get name(): string {
        return this._name;
    }

    /**
     * ... property.
     */
    set name(value: string) {
        if (this.checkName(value)) {
           this._name = value;
        }
    }

    /**
     * Do something
     */
    private checkName(name: string): boolean {
         return name && name.trim();
    }
}

Please consider to add additional ways to define properties, similar to C# where you also have more than one way.

Additional ways in a future release

class MySecondClass {
    /**
     * Creates a new Instance of this class
     */
    constructor(id: number, private _name: string) {
        this.id = id;
    }

    /**
     * Define a property in only one line
     */
    public id(): number { get; set; }

    /**
     * Define a property with additional functionallity
     */
    public name(): string {
        get { return this._name; }
        set { 
           if (this.checkName(value)) {
              this._name = value; 
           }
        }
    }

    /**
     * Do something
     */
    private checkName(name: string): boolean {
         return name && name.trim();
    }
}

Out of Scope Suggestion

Most helpful comment

But it does mean it is against the design goals of TypeScript. There are lots of things that could be lots of things. Dart is an example of a language that is "JavaScript-like" but allows other constructs. TypeScript is not Dart.

All 11 comments

That breaks is a TypeScript non-goal:

  1. Exactly mimic the design of existing languages. Instead, use the behavior of JavaScript and the intentions of program authors as a guide for what makes the most sense in the language.

TypeScript follows the pattern of ECMAScript. If you want different ways of setting accessors, then the best place to request that functionality is via TC39 and then of course TypeScript would follow the lead.

But that doesn't mean, that you can't make the language much easier in use and much more readable and structured.

What I wrote was just an example how it could be.

But it does mean it is against the design goals of TypeScript. There are lots of things that could be lots of things. Dart is an example of a language that is "JavaScript-like" but allows other constructs. TypeScript is not Dart.

@NCC1701M
One reason for this design tenet is that TS wants to stay compatible with future JS standards.

What if in the future JS chooses a different syntax for simplified properties? This happened in the past with modules and it was a little messy to recover from. Worse: what if JS introduces a new syntax that is plain incompatible with your proposed syntax?!

As kitsonk said, you probably should raise your voice at TC39 first so that the idea can trickle down from JS to TS.

BTW, _except for auto properties_ I don't think there's much to gain here. C# syntax is as long as JS. And since in JS there is no practical difference between a field and an auto-property (is there?) I wonder why you want them?

What the above commenters said.

I'd also note that

public id(): number { get; set; }

is much easier to write already:

id: number;

This might be a corner case here. But I actually came across a scenario in which the difference between a field and an auto-property does exist.

Say that you want to put a property decorator @foo on some arbitrary classes' properties. By arbitrary I mean that those classes do not necessarily have identical constructor signatures.

import 'reflect-metadata'
function foo(msg: string) {
  return Reflect.metadata('foo', msg)
}
class A {
  @foo('field1')
  field1: string
  private _field2: string
  @foo('field2')
  get field2(): string { return this._field2 }
  set field2(value: string) { this._field2 = value }
}

Then you want to inspect each class and get a list of properties that are decorated with @foo.

function inspect(target: any): string[] {
  return Object.keys(target).filter((key) => Reflect.hasMetadata('foo', target, key))
}

For some reason you don't have access to an instance when you call inspect and you can't really instantiate one by yourself since the constructor signature is unknown, so you are stuck with inspect(A.prototype).

inspect(A.prototype)
// Wants: ['field1', 'field2']
// Gets: ['field2']

A field is not defined on prototype, so you have to define all fields as properties, which could be very messy without auto-properties.

class A {
  private _field1: string
  @foo('field1')
  get field1(): string { return this._field1 }
  set field1(value: string) { this._field1 = value }
  private _field2: string
  @foo('field2')
  get field2(): string { return this._field2 }
  set field2(value: string) { this._field2 = value }
  // ....
  // 998 properties later (not really but you got the idea)
  private _field1000: string
  @foo('field1000')
  get field1000(): string { return this._field1000 }
  set field1000(value: string) { this._field1000 = value }
}

@akfish I am no specialist of the proposed reflect-metadata but isn't that a problem with your inspect implementation?

Calling Object.keys on the prototype will not provide you any instance members (like fields).
Yet, I think the metadata for fields should be available on the prototype.

Have you tried Reflect.getMetadataKeys(target)?

@jods4

Calling Object.keys on the prototype will not provide you any instance members

Yep, I know it's by-design. That's why in this specific case (having no access to instances), fields and auto-properties are not interchangeable.

Have you tried Reflect.getMetadataKeys(target)?

According to the code doc of Reflect.getMetaKeys(target), it only gives you the metadata keys declared on the target, not those on its members. You will have to know member's name (aka the propertyKey) to get the metadatas on that member.2 And Reflect does not provide API for discovering propertyKeys. Hence the need of calling Object.keys.

I'm not looking for a solution for this specific issue (I implemented the decorators differently in my actual project to support field members), nor am I pushing for this feature (I understand the design concerns mentioned above), just providing an example for informative reason.

@akfish You are right Reflect.getMetadataKeys needs the property name, my mistake. It's a strange design that all instance metadata is stored and available on the prototype, but there is no public API to iterate it without prior knowledge.

I know that's not the main point here, I was just (badly) reflecting on the given example.

I know this is an old post, but I want to add an argument to this discussion.
Most arguments I have seen did not address why I think auto properties are so valuable. Namely different access modifiers for the getter and the setter.

Lets consider the next sample, which I use pretty often.

class A {
   protected _field: number;
   public get field() { return this._field; }
   protected  set field(v: number) { this._field = v; }
}

I want to expose the ability to get a certain field, but I don't want it to be set outside of A and it's children.

In (for example) C# you can do the following:
C# class A { public field { get; protected set; } }
Besides that it easier to read and shorter, it also just introduces one field less (namely _field). In the TS version you could remove the setter as it has the same protection level as the field itself, but in that case you would have to use the _field for internal use and the field for external getting. Again possible, but in my opinion less readable.

My last argument for the auto property is about documentation, In typescript I would have to add a documentation comment above at most 2 fields, while with auto properties I could just use 1 comment.

I am not saying TypeScript should mimic the C# implementation, but some sort of auto properties would be nice. I myself like the getter setter pattern described above allot; it gives the programmer a good way to translates it's intent to other coders. But as of now writing and maintaining them is more work than it could be.

TypeScript already creates a variable named _this for lambdas. Why not create a property named _id and allow "public Id { get; set; }" ? This would transpile to the existing onerous way of creating getters and setters in JavaScript.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

zhuravlikjb picture zhuravlikjb  ·  3Comments

fwanicka picture fwanicka  ·  3Comments

MartynasZilinskas picture MartynasZilinskas  ·  3Comments

kyasbal-1994 picture kyasbal-1994  ·  3Comments

dlaberge picture dlaberge  ·  3Comments