Skip to content

Instantly share code, notes, and snippets.

@controlflow
Last active August 29, 2015 13:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save controlflow/9996665 to your computer and use it in GitHub Desktop.
Save controlflow/9996665 to your computer and use it in GitHub Desktop.
Possible C# primary constructors design
class Person : EntityBase {
// ~familiar syntax, no problems with xml doc, can omit body, can omit public?
public constructor(int id, string name, int age) : base(id);
readonly string _mrName = "Mr. " + name;
public string Name { get; } = name;
public int Age { get; } = age;
}
class Person {
// can change visiblity and add body for checks
private constructor(string name, int age) {
if (name == null) throw new ArgumentNullException("name");
}
public Person() : this("Foo", 42) {
// secondary
}
public string Name { get; } = name;
public int Age { get; } = age;
}
class Component {
// explicit capture, can omit private when readonly
[ImportingConstructor] // no problems with attributes
public constructor([NotNull] readonly IFoo foo, [NotNull] readonly IBar bar);
public IFoo ExposedFoo => foo;
public void M(foo) {
// capture declares ordinary fields from class parameters,
// so conflicts with locals can be resolved by 'this.' qualifier
... this.foo ...
... bar ...
...
}
}
// placing primary constructor inside class body prevents from ability to put tons of code
// outside of class declaration body block, keeping class name and extends clause clear.
// I'm expecting silly classes like this:
class Component(IFoo dependency, IBar other, IBlah anotherOne)
: MySuperBaseType(dependency, other, anotherOne, (HowPeopleLove to) => {
put.LambdaExpressions(Inside.Base.Initializer);
InsteadOfOverridingMethods();
HowIHateThis();
}), ISomeInterface, IOtherInterface
{
}
@ViIvanov
Copy link

ViIvanov commented Apr 5, 2014

may be just

class Person : EntityBase {
  public(int id, string name, int age) : base(id);

  public string Name { get; } = name;
  public int    Age  { get; } = age;
}

@controlflow
Copy link
Author

@Vilvanov, maybe, but this can introduce grammar ambiguities... I don't know for sure, but I also don't like constructor or maybe new as a name...

@ViIvanov
Copy link

ViIvanov commented Apr 5, 2014

About

(HowPeopleLove to) => {
    put.LambdaExpressions(Inside.Base.Initializer);
    InsteadOfOverridingMethods();

What do you think about a virtual calls from a constructors? If base class need to call some method from derived in own ctor? I do not like lamdas/delegates in a parameters and I use a classes special for this cases :o( Sorry for off-topic :o)

@ashmind
Copy link

ashmind commented Apr 5, 2014

Even though I understand your reasoning, I'm not a fan of this syntax -- the perceived scope of arguments looks much more sensible in the current Roslyn syntax.

Attributes can be solved with a new attribute target, or just using "method:" though it looks worse. XML doc should be updated to support param, but summary is pretty useless for primary constructor anyway.

@ashmind
Copy link

ashmind commented Apr 5, 2014

The lambda-parameter-with-logic-as-argument-to-base is something I haven't really seen at all -- what are some examples?

@controlflow
Copy link
Author

@ashmind,

class Person {
  constructor(public name: string, public age: number) { }
  get mrName() {
    return "Mr. " + this.name;
  }
}
  1. Yep, scoping looks 'strange', but it is done the same as TypeScript design and it feels fine:
  2. Attribute specifier "constructor:" exists in C#, but users don't know about it;
  3. Provide you with examples of shitty code because you don't 'seen it at all'? Maybe you can just believe me, because I've already kill code like this in our code base?
  4. I like the F#-style placement of primary constructor too (type Person(name, age)), but:
  • It is hard to provide syntax for explicit body to do side-effects like argument checks and event subscriptions;
  • XML documentation is the issue, you will unable to write famous useless summary like "Initializes the new instance of StringBuilder class";
  • It is hard to find a place for visibility modifier;
  • Class declaration upper side to looks messy - sometimes you will not be able to easily find base type specification or interface implementation;
  • Just compare:
  [Language(typeof(JavaScriptLanguage))]
  public class JavaScriptLanguageService : JavaScriptBasedLanguageService
  {
    private readonly IJsTypeResolver myTypeResolver;
    private readonly IJavaScriptCodeFormatter myCodeFormatter;

    public JavaScriptLanguageService(
      JavaScriptLanguage language, IConstantValueService constantValueService,
      IJavaScriptCodeFormatter codeFormatter, IJsTypeResolver typeResolver)
      : base(language, constantValueService)
    {
      myTypeResolver = typeResolver;
      myCodeFormatter = codeFormatter;
    }

  [Language(typeof(JavaScriptLanguage))]
  public class JavaScriptLanguageService : JavaScriptBasedLanguageService
  {
    public constructor(
      JavaScriptLanguage language, IConstantValueService constantValueService,
      private readonly IJavaScriptCodeFormatter codeFormatter,
      private readonly IJsTypeResolver typeResolver)
      : base(language, constantValueService);


  [Language(typeof(JavaScriptLanguage))]
  public class JavaScriptLanguageService(
      JavaScriptLanguage language, IConstantValueService constantValueService,
      private readonly IJavaScriptCodeFormatter codeFormatter,
      private readonly IJsTypeResolver typeResolver)
    : JavaScriptBasedLanguageService(language, constantValueService)
  {

@ashmind
Copy link

ashmind commented Apr 6, 2014

Attribute specifier "constructor:" exists in C#
Are you sure? C# 5 spec lists the following:

global-attribute-target:
  assembly
  module

attribute-target:
  field
  event
  method
  param
  property
  return
  type

Provide you with examples of shitty code because you don't 'seen it at all'? Maybe you can just believe me, because I've already kill code like this in our code base?

Maybe it is specific to your codebase though, people just learning incorrectly from existing code. I do not disbelieve this exists, I am just trying to understand why anyone would use it.

It is hard to provide syntax for explicit body to do side-effects like argument checks and event subscriptions;
It is not that hard (just stuff the {} somewhere), but I would argue it is unnecessary for the primary constructor. You can always fall back to using normal constructor if you have complex logic. And argument checks can be easily provided btw:

 public string X { get; } = Argument.NotNull(x);
 // or even (shittier)
 public string X { get; } = (
     Argument.RequireNotNull(x);
     x;
 ); // if I understand ; operator correctly

XML documentation is the issue, you will unable to write famous useless summary like "Initializes the new instance of StringBuilder class";
As I said, and you say, this is useless, and especially useless for primary constructor.

It is hard to find a place for visibility modifier;
I agree, but I do not see a problem with limiting these to public (protected for abstract) as that would be most common use case. You can always just write a normal constructor if you want more control.

Class declaration upper side to looks messy - sometimes you will not be able to easily find base type specification or interface implementation;

This is subjective, I do not find your third example hard-to-read.

However, I do find this weird:

class Person {
  public string Name { get; } = name;
  // ...
  // ...
  // ...
  constructor(string name) { }
}

@ashmind
Copy link

ashmind commented Apr 6, 2014

In general I see primary constructors as something similar to autoproperties -- they are not a silver bullet for everything.
If you need something uncommon such as private constructor or event subscription in constructor, you can fall back to using normal constructor. Though if second part is common, it can be done by allowing a code block one way or another. I am just not sure it is common.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment