csharplang/meetings/2013/LDM-2013-10-21.md
Nick Schonning 6cd82c21ed fix: MD033/no-inline-html
Inline HTML get swallowed in MD and HTML rendering
2019-05-25 01:31:46 -04:00

11 KiB
Raw Blame History

C# Design Notes for Oct 21, 2013

Agenda

Primary constructors is the first C# 6.0 feature to be speced and implemented. Aleksey, who is doing the implementing joined us to help settle a number of details that came up during this process. We also did a rethink around Lightweight Dynamic.

  1. Primary Constructors <fleshed out a few more details>
  2. Lightweight Dynamic <we examined a much simpler approach>

Primary Constructors

Primary constructors were first discussed in the design meeting on Apr 15, where we decided to include them, but have been around as a suggestion for much longer. It has great synergy with features such as initializers on auto-properties (May 20), because it puts constructor parameters in scope for initializers.

The evolving “speclet” for primary constructors can be found in the “Csharp 6.0 Speclets” working document in the design notes archive (see link above). It reflects a number of implementation choices, and also (at the time of the meeting) left some issues open, which we settled as best we could. Some decisions may change as we get experience with the feature indeed the early focus on implementing this feature is so that we can incorporate feedback from usage.

Partial types

Primary constructors involve a parameter list on “the” class or struct header, as well as an optional argument list on “the” base class specification. But what if the type declaration is split over multiple partial types?

There is different precedence with partial classes. Sometimes elements can occur in only one part (members), sometimes they must occur and be the “same” in all parts (type parameters), sometimes they are optional but must agree where present (accessibility and constraints) and sometimes they are cumulative over parts (attributes).

There seems to be little value in allowing class parameters for primary constructors to occur in multiple parts indeed it would require us to come up with a notion of sameness that only adds complication to the language. So class parameters are allowed only on one of the parts. Arguments to the base class are allowed only in the part where the class parameters occur. The class parameters are in scope in all the parts. This is also the approach that most resembles what is the case today: constructor parameters occur only in one place, and the privates used to hold their values are in scope in all the parts.

User-provided constructor body

As currently specified, primary constructors give rise to a constructor that is entirely compiler-generated. There is no syntax for the developer to specify statements to be executed as part of the primary constructor invocation. This is less of an issue than it might seem, since more than 90% of what people do in constructor bodies is probably what primary constructors now do for them copying parameter values into private fields. Even initialization of other fields can now usually take place in initializers, because the constructor parameters are in scope for those.

Even so, for the remaining scenarios the user would fall off a cliff: they would have to renounce primary constructors and turn them back into explicit constructors. This is a shame, and it is definitely worth considering what syntax we would use to fix this.

A radical idea is to just allow statements directly in the class body. This would be weird for several reasons though. Aside from syntactic issues, theres the problem of evaluation order relative to fields with initializers: initializers run before the base call, whereas statements in a constructor body usually run after, so interspersing them would have very surprising results. In reality wed need to place the statements in a block, and the only question is how we “dress up” the block if at all. Some ideas:

class C(int x): B(x)
{
    // Repeat the full signature  redundant and error prone
    C(int x) { Register(this); } 
    // Class name and empty parens  clashes with noarg constructor syntax, 
    // but similar to static constructors
    C() { Register(this); }
    // Just the class name  a little unfamiliar
    C { Register(this); }
    // Prefix with this keyword  again a bit unfamiliar
    this { Register(this); }
    // Just the block  succinct but mysterious  and has the eval order issue
    { Register(this); }
}

Many of these options are reasonable. Well hold off though and see how the feature fares without user specified statements for now.

Struct issues

Structs always have a default constructor. Should we allow explicit constructors in a struct with a primary constructor to delegate to the default constructor instead of the primary constructor? I.e. is the following ok?

struct S(int x)
{
    public S(bool b) : this() {  }
}

It seems that the restriction on explicit constructors to always chain to the primary constructor cannot be upheld just by requiring a this(…) initializer on them: we must also require that it isnt referencing the default (no-arg) constructor.

Also, members in structs are allowed to assign to this. Should we allow that also in constructor bodies when there are primary constructors? We probably cannot reasonably prevent it, and people who do this are already in the advanced category. But we have to spec that it causes the primary constructor parameters to be reset to their default value.

Attribute targets

Currently, we allow “method” targets on constructors. We should allow an attribute on a class or a struct with a primary constructor to specify a “method” target in order to be applied to the underlying generated constructor.

We should also allow attributes on the individual parameters of the primary constructor to specify a “field” target in order to be applied to the underlying field. In practice there may be optimizations so that the field is not generated when not needed, but the field target should force the field to be generated.

Accessibility on primary constructor parameters

There is an idea known from other languages (e.g. Scala, TypeScript) of allowing accessibility modifiers on class parameters. This would mean that a property is generated on the class with the name of the parameter, and the accessibility specified.

This would make some scenarios even more succinct, but it is not a straightforward feature. For instance, parameter names are typically lower case and property names typically upper case. We think this is a feature that can be added later if we feel there is significant further gain. Right now we are willing to bet on the combination of primary constructors and more terse property specification (e.g. with expression bodied properties and initializers for auto-properties) as a sufficiently great advance in the specification of data-like classes and structs.

Conclusion

We continue to bake the details of primary constructors.

Lightweight Dynamic

We took a fresh look at Lightweight Dynamic, based on feedback from Dino, who used to work on the “old” dynamic infrastructure.

The purpose of Lightweight Dynamic is to allow the behaviors of operations on an object to be specified programmatically, as part of the implementation of the objects class. Dino pointed out that most behaviors can already be specified, using operator overloading, user defined conversions and indexers. Really, the “only” things missing are invocation and dotting. So instead of coming up with yet another new infrastructure for programmatic definition of object behaviors, we should just augment the one weve had all along with facilities for invocation and member access.

Doing more with less is a pretty attractive idea! So we experimented with what that would look like.

Invokers

It seems reasonable to allow specification of what it means to invoke an object. The scenario seems similar to indexers, which are a special kind of instance members that can be abstract and virtual, take arguments, and be overloaded on signature.

The main difference would be that parameters are specified in round parentheses, and that the body is simply a method body, rather than a pair of accessors:

public int this(int x) { return x*x; } // allow instances to be invoked with ints

One could imagine using this feature to build alternatives to delegate types, but more interestingly, this could provide semi-typed invocation capabilities for e.g. service calls:

public object this(params object[] args) {  } // generalized dynamic invoker

While it is straightforward to imagine this feature, it is also of relatively limited value: You can always provide an explicit Invoke method that the user can call at little extra cost.

myOperation.Invoke(5, "Hello"); // instead of
myOperation(5, "Hello");

Even so, it is interesting to consider.

Member access

For programmatic member access it seems superficially attractive or at least powerful to allow overriding of the dot operator. Maybe in the form of some kind of operator overload?

public static JsonObject operator .(JsonObject receiver, string name) {  }

The problem of course is that dot is such a fundamental operator. If you overload it to hide the native meaning, you can hardly do anything with the underlying object. How would you even implement the dot operator without using the built in dot for accessing state etc. on the object?

You could of course have rules like we do for events, where the meaning is different on the inside and the outside. But that is not exactly elegant. The fact of the matter is that overriding dot on a statically typed class is simply too disruptive.

Here is where Visual Basic may provide some interesting inspiration. The typical way youd implement the dot operator would be some sort of dictionary lookup, and in fact youd probably be fine if dotting did the same as indexing with a string, only with slightly more elegant syntax. Well, VB has an operator for that: The lookup operator, !.

We could essentially resign ourselves to not using dot for “lightweight dynamic” member access, instead using ! as a just-as-short substitute. We would then simply define these two expressions to mean the same:

obj!x
obj["x"]

If you want to implement dynamic behavior for member access, you just provide an indexer over strings. Your callers will have to use a ! instead of a ., but that is probably a good thing: that way they can still access your “real” members using a normal dot. This was a real issue with the Lightweight dynamic model weve explored before, one that we had yet to find a good solution to.

Conclusion

We still need to dig deeper here, but we think that adding the ! lookup operator as a short hand for indexing with a string may quite possibly be the only work we have to do in the language for lightweight dynamic! That is quite possibly the biggest reduction of work associated with a feature weve ever accomplished in a single sitting!