Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow calling base class constructor in the middle of derived class constructor #2335

Closed
munael opened this issue Mar 12, 2019 · 44 comments
Closed

Comments

@munael
Copy link

munael commented Mar 12, 2019

To allow

class Derived : Base {
    Derived(Foo foo) {
        // do bar and baz with foo
        base(bar, baz);
        //  blah
    }
}

Instead of having to extract the do bar and baz section into static functions (one for each argument in the base constructor that needs modified values). If we can already functionally do this, there should be no need for the extra complication.

Any standing theoretical reasons for the restriction?

@HaloFour
Copy link
Contributor

@narfanar

Any standing reasons for the restriction?

It's easier to comprehend that you're very limited as to what you can do before the base constructor call when the syntax forces it on you. Yes, you can make your code slightly less verbose by eliminating the need for a separate function, but it's a suitable workaround for what is not a terribly common situation.

@CyrusNajmabadi
Copy link
Member

Any standing reasons for the restriction?

Yes, the language doesn't allow it.

As for why it doesn't allow it, the general reason is around the complexity of the programming model, especially around initialization, virtual-calls, and whatnot.

@HaloFour
Copy link
Contributor

HaloFour commented Mar 12, 2019

@CyrusNajmabadi

As for why it doesn't allow it, the general reason is around the complexity of the programming model, especially around initialization, virtual-calls, and whatnot.

I get the sense from the question that they're not looking to ease the restrictions, only allow the restricted code to exist before the base constructor call.

@munael
Copy link
Author

munael commented Mar 13, 2019

@narfanar

Any standing reasons for the restriction?

It's easier to comprehend that you're very limited as to what you can do before the base constructor call when the syntax forces it on you. Yes, you can make your code slightly less verbose by eliminating the need for a separate function, but it's a suitable workaround for what is not a terribly common situation.

The verbosity (and even wasted work) rapidly spiral out of control once several arguments of the base class constructor need to be themselves prepared from the arguments of the derived class constructor.

Verbosity because then we need separate static functions (and calls) to get the value of each argument passed to the base constructor, and wasted work if it happens that several such arguments can share parts of their preparation functions.

D allows this. I remember there were issues with virtual functions and some order of construction stuff. Will see if there're salient lessons there that could help the case of this issue.

@YairHalberstadt
Copy link
Contributor

and wasted work if it happens that several such arguments can share parts of their preparation functions.

Whilst a hacky workaround, you could use out parameters to avoid this wasted work.

@dsaf
Copy link

dsaf commented Mar 13, 2019

@narfanar

Any standing theoretical reasons for the restriction?

https://en.wikipedia.org/wiki/Halting_problem

@munael
Copy link
Author

munael commented Mar 13, 2019

@YairHalberstadt

How would you out into the arguments of the base constructor? Can you provide an example?

@dsaf

@narfanar

Any standing theoretical reasons for the restriction?

https://en.wikipedia.org/wiki/Halting_problem

Sorry? I'm not following.

@YairHalberstadt
Copy link
Contributor

Public class A
{
    public A(int a, int b) {}
}
Public class B : A
{
    public B(string str) : base(GetBaseParams(str out int b), b){}
    public int GetBaseParams (string str, out int b)
    {
         strs = string.Split(str);
         b = int.Parse(strs[1]);
         return int.Parse(strs[0]);
    }
}

@Korporal
Copy link

Korporal commented Mar 13, 2019

@CyrusNajmabadi

Any standing reasons for the restriction?

Yes, the language doesn't allow it.

Another vacuous reply from Cyrus! Given that his question literally means "Any standing reasons for the language disallowing it?" I can only conclude that exaggerated self-importance often motivates much of what your say here.

@yaakov-h
Copy link
Member

@Korporal and then Cyrus addresses the reasons in literally the next line. Personal attacks are not appreciated here.

@HaloFour
Copy link
Contributor

I'm kind of on the fence about this. I have written classes that had to manipulate/validate parameters before passing them along to a base constructor before, pre-C# 7.0, and it was painful as all Heck. With out parameters it's considerably less painful since you can pass the arguments along to a static method that does the work and passes them back, but that's still not exactly pleasant and it moves the initialization code far away from where it's relevant. It seems that it should be safe to allow arbitrary statements before base(...) as long as this is inaccessible (including all instance members). IIRC that's what the CLR allows, it's just that the current C# syntax doesn't allow anywhere to put those statements.

There could be two ways of accomplishing this syntax-wise. You could have a block before the base constructor call:

private readonly int y;

public Derived(string x, int y) {
    // static stuff here only
    if (string.IsNullOrEmpty(x)) throw ArgumentException();
} : base(x) {
    // can access instance members here
    this.y = y;
}

Or allow base to appear like a method call within the constructor body:

private readonly int y;

public Derived(string x, int y) {
    // static stuff here only
    if (string.IsNullOrEmpty(x)) throw ArgumentException();
    base(x);
    // can access instance members here
    this.y = y;
}

The first trades some verbosity for very clear delineation of the sections of code.

@YairHalberstadt
Copy link
Contributor

I'm leaning towards the first option, but http://gafter.blogspot.com/2017/06/making-new-language-features-stand-out.html?m=1

@theunrepentantgeek
Copy link

I cut my teeth as a professional developer using Delphi - and its Object Pascal explicitly allowed/required a call to the base constructor as a regular statement.

It was common to find subtle object initialization bugs where certain instance methods would only work after the base constructor call. Sometimes, when we were lucky, they'd throw. Other times, they'd return incorrect or inconsistent results.

I vividly remember having to untangle some chicken-and-egg scenarios where function Foo() would only give correct results after the base constructor was called, but the result of Foo() was needed to make that call.

@CyrusNajmabadi
Copy link
Member

Another vacuous reply from Cyrus!

I literally explained why in the post you snipped from and didn't quote fully. The language doesn't allow this because of "the general reason [...] around the complexity of the programming model, especially around initialization, virtual-calls, and whatnot."

That is truthful and accurate and appropriate given the question.

@Korporal
Copy link

@CyrusNajmabadi

Another vacuous reply from Cyrus!

I literally explained why in the post you snipped from and didn't quote fully. The language doesn't allow this because of "the general reason [...] around the complexity of the programming model, especially around initialization, virtual-calls, and whatnot."

That is truthful and accurate and appropriate given the question.

Well Cyrus I'm afraid I've found you to be somewhat supercilious in many of your exchanges, a person asks "Any standing reasons for the restriction?" and you simply cannot resist trying to appear erudite by responding "Yes, the language doesn't allow it." which is a tautology.

Most of us would have omitted that and simply said the second part of your response. You must understand that the tone and character one uses in these posts is an important aspect of communication be it technical or otherwise, I simply think you need some work in this area.

@CyrusNajmabadi
Copy link
Member

CyrusNajmabadi commented Mar 13, 2019

and you simply cannot resist trying to appear erudite by responding "Yes, the language doesn't allow it." which is a tautology.

I literally gave the reason in the very next sentence. It was one full thought here. I included thefirst part because sometimes people view these issues as potentially bugs in the compiler and not as per the exact and specific design of the language. I was stating emphatically that this was an intentional decision in the language, and i then followed immediately with teh reasoning for why it was an intentional decision.

It is important as part of the discussion because these can be separate issues with differing rules about what is done with them. For example, if this was not per the language rules (which does happen), we could talk about what we should then do about that. But if this is per the language rule, then this becomes a discussion about why it is that way. I intentionally specified that this is by design, and the gave information on why the design is the way it is.

Most of us would have omitted that

And i would not. Because it's important to recognize the intentional decisions from the unintentional ones especially in the domain of language design.

@HaloFour
Copy link
Contributor

To argue against myself it's also just as easy to use a private constructor with a static factory method:

public class A
{
    public A(int a, int b) {}
}
public class B : A
{
    private B(string a, int b) : base(a, b)
    {
        // more init here
    }

    public static B Create(string str)
    {
        // validation and pre-init here 
         var strs = string.Split(str);
         int b = int.Parse(strs[1]);
         var instance = new B(strs[0], b);
        // post-init here, if necessary
        return instance;
    }
}

@juliusfriedman
Copy link

juliusfriedman commented Mar 31, 2019

Please show a real life example of how this is useful as the underlying base constructor must be called anyhow..

For the layman technica you can't pass it around as the base instance until you do so and that base may still throw an exception; furthermore where are you going to get allocated when you define the class? so I am baffled by this proposal.. unless it's for custom allocator semantics which allow control over such...

You would / could use GetUnitializedObject and composition to achieve this now as well as unsafe code but I still don't understand why you would want to do this as it's asking for problems.

@HaloFour
Copy link
Contributor

@juliusfriedman

The C# compiler does this all the time, in that you're allowed to have expressions in the base() call. That's compiled into IL that executes before the base constructor call, and as long as that IL doesn't attempt to access this it's perfectly fine. Where it becomes a problem is that you can only have expressions, and those expressions can only have very limited interaction with one another. So if you want to do something like validate that the constructor arguments are valid before calling the base constructor you have to extract that out into a separate static method which returns a value that you can pass to the base constructor.

@juliusfriedman
Copy link

Yes but shouldn't the base class have that responsibility i.e. if it changes in the future...

Also since the interaction is limited to self reference but explicitly bad when accessing the base Pointer itself then what's the use?

So long as this cannot be used I think you mean from dervied as they check with call virt if call virt is not used this is still fine but only on the members defined on this in that type...

Tldr, how can this be useful and not bad?

Now with what I was saying about constraints being utilized in the constructor via modreqs.. I see that enhancing generics such as to support ctor arguments would be needed for this as well .. #769

@HaloFour
Copy link
Contributor

@juliusfriedman

Yes but shouldn't the base class have that responsibility i.e. if it changes in the future...

Why should the base class be at all aware of what a derived constructor might want to do?

Also since the interaction is limited to self reference but explicitly bad when accessing the base Pointer itself then what's the use?

The interaction would be limited to static members of the type as well as the constructor arguments themselves. The use would be to do something with those constructor arguments before calling the base constructor, such as validating them.

Tldr, how can this be useful and not bad?

An example would be wanting to validate that the constructor arguments are valid before calling a base constructor that might do something expensive. Another example would be deriving the base constructor arguments from the derived constructor arguments through some calculation. Anything that would be nicer to express as statements rather than trying to pack into one or more isolated expressions, e.g.:

public class Derived : Base {
    private string value;
    public Derived(string value) {
        if (value == null) throw new ArgumentNullException(nameof(value));

        // base constructor does something very expensive
        // that you want to avoid if the arguments aren't valid
        base();
        this.value = value;
    }
}

And I'll note again that all of this is perfectly legal as far as the CLR is concerned, and C# takes advantage of this fact by virtue of allowing you to execute expressions to derive the base constructor arguments. The limitation against invoking other arbitrary code (that doesn't attempt to access this) is imposed by the C# compiler.

@juliusfriedman
Copy link

juliusfriedman commented Mar 31, 2019

@HaloFour

It sounds like a violation of the chain of responsibility, the base class should validate its own parameters, ifcase then they change the validate logic is then wrong.

I have wanted to do that for reasons you cite such as validation of parameters but it usually ends up in duplicate code with no benefit to the user or maintainer, what is more usual is adjusting the parameters passed to base in such conditional statenents such as null being pass as empty or -1 implying to check another parameters like length and then defaulting to empty if null etc which again should probably be handled in the base class.

Tldr...

Finally, how will you prevent double initialization here? E.g. if I call base twice or with different arguments each time etc .. what if I call it in a different thread? What about guarantees about the construction in a static context..

So much to consider and adjust for with little demonstrated benefit ..

Qi Bono?

Tody you can just pass an action\task to the Ctor which can do this without the side effects and maintains safety until you shoot yourself in the foot.

This feature will give everyone a loaded gun...

@HaloFour
Copy link
Contributor

@juliusfriedman

the base class should validate its own parameters

Yes, the base class should validate its own parameters, just like the derived class should validate its own parameters. The problem is that, currently, the derived class can't validate its own parameters until after the base class constructor is invoked, unless you contort your logic into expressions that can be passed to the base class. If the base class accepts no parameters, you're completely out of luck.

@juliusfriedman
Copy link

juliusfriedman commented Mar 31, 2019

@HaloFour

In what situation would that be useful, if the property isnt virtual you cannot access it from the derived until you call base ctor.

If your base ctor accepts no arguments and you must validate something on it before you construct it then favor composition over inheritance for the poorly design base class.

That is a classic case of a paradox, you can't validate something which doesn't exist until after you create it.

In the extent that the object instance requires the state of some static type or a Singleton which is aggravated by access then reflection or Emit becomes the conical way of dealing with such AFAIK.

@HaloFour
Copy link
Contributor

@juliusfriedman

This feature will give everyone a loaded gun...

I don't understand why you think this. Maybe we're not on the same page? As mentioned, you're free to do this already in C# in that you can execute arbitrary expressions and pass their results to the base constructor arguments.

In what situation would that be useful, if the property isnt virtual you cannot access it from the derived until you call base ctor.

Nobody is talking about properties. I'm talking explicitly and exclusively about the parameters to the derived constructor.

That is a classic case of a paradox, you can't validate something which doesn't exist until after you create it.

Again, I'm not talking about validating the object which you are trying to construct before you've constructed it.

@HaloFour
Copy link
Contributor

@juliusfriedman

Finally, how will you prevent double initialization here? E.g. if I call base twice or with different arguments each time etc .. what if I call it in a different thread?

This is indeed the only tricky aspect about a feature like this. The compiler would have to enforce that you invoke the base constructor exactly once within that method and that the only thing that you can do with the base constructor is to invoke it.

@juliusfriedman
Copy link

juliusfriedman commented Mar 31, 2019

@HaloFour, see above...

Double initialization; different initialization; both combined with some type of threading and then used as a static instance.

It's one thing to have the construct in IL which is not the topic of discussion here.

My problem would be letting the compiler do this as again what benefit is there besides chaos which can and will likely ensure this becomes more aggravated than it is already.

Furthermore the only use would be a niche object which had a contract that it would perform different logic in different constructors and would need both to be called, the solution to that is as you have provided above in the example, write an init method or two and call them as needed.

Finally structs also have a consideration as I have to assign all members before return or call base first... if struct inheritance is ever championed that could change but still a consideration.

@HaloFour
Copy link
Contributor

@juliusfriedman

My problem would be letting the compiler do this as again what benefit is there besides chaos which can and will likely ensure this becomes more aggravated than it is already.

There is no chaos here. You're reading something into this proposal that isn't there.

Furthermore the only use would be a niche object which had a contract that it would perform different logic in different constructors and would need both to be called, the solution to that is as you have provided above in the example, write an init method or two and call them as needed.

Derived classes may always impose a different contract on construction from the base class. That's why they can have their own constructors. The derived constructor is purely additive, though, and this proposal does nothing to change that. You can't change how the base constructor works, nor can you avoid invoking it (short of throwing an exception).

Sure, you can achieve the same thing with an init method, and I mentioned that above. But the one issue that an init method brings is that it makes it more awkward to extend from the derived class as you can no longer chain constructors.

As for whether or not this is niche, I'd suggest that it's not niche to want to validate arguments early, nor is it niche for a derived class to expose different constructor parameters than the base class.

@HaloFour
Copy link
Contributor

@juliusfriedman

Here's an example of how you can workaround the language limitation today, although it only works if there is an overload of the base constructor that accepts arguments:

public class Base {
    protected Base(int a, string b, Guid c) => Thread.Sleep(TimeSpan.Infinity);
}

public class Derived : Base {
    public Derived(SomeType t) : base(PreInitialize(t, out var b, out var c), b, c) {
        // other init here
    }

    private static int PreInitialize(SomeType t, out string b, out Guid c) {
        if (t == null) throw new ArgumentNullException(nameof(t));
        int a = CalculateA(t);
        if (a < 0) throw new InvalidOperationException();
        b = CalculateB(t, a);
        c = CalculateC(t, a, b);
        return a;
    }
}

Before out-declarations this was a lot more awkward to write since you couldn't otherwise declare those variables, so you needed three separate static methods which would likely involve code duplication.

@juliusfriedman
Copy link

juliusfriedman commented Mar 31, 2019

@HaloFour

Could you also not have a ParameterValidator T and do this in an OOP way...

There's no reason to divorce the construction process to support parameter validation iMHO.

What other languages support this? JavaScript?

What usefulness will this lend to existing paradims? Besides the contrived..

Tldr;

I appreciate your willingness to think out of the box!

I will take back chaos by saying it's good you will be able to now express something you could in IL from C# however care should be taken to ensure that we don't open a can of worms and that the effort expended to champion the proposal will be outweighed by the benefit it provides which iMHO has not been shown.

Tldr; I don't want this to result in reinit. Especially with structs because assignments made before that point to the instance will be erased by the init call to this unless you expense more energy to ensure that the enhancement doesn't work for structs unless before assigned.

@HaloFour
Copy link
Contributor

@juliusfriedman

Why not have a ParameterValidator T and do this in an OOP way...

That adds more complexity and moves the logic away from where it is relevant.

There's no reason to divorce the construction process to support parameter validation iMHO.

It doesn't divorce the construction process, it only enables more code constructs to exist before the base constructor call.

What other languages support this? JavaScript?

D does. Check out 15.8.9 of the D spec. In particular:

  1. If a constructor's code contains a delegate constructor call, all possible execution paths through the constructor must make exactly one delegate constructor call:
  2. It is illegal to refer to this implicitly or explicitly prior to making a delegate constructor call.

In D the "delegate constructor" is the base constructor.

I'm not aware of other OOP languages that the developer to explicitly do this, although I think all OOP languages allow some code to execute before the base constructor call for field initializers as well as the previously mentioned expressions evaluated for the base constructor arguments.

I will take back chaos by saying it's good you will be able to now express something you could in IL from C# however care should be taken to ensure that we don't open a can of worms and that the effort expended to champion the proposal will be outweighed by the benefit it provides which iMHO has not been shown.

Sure, I'm not going to argue against that point. Of course it also applies to literally every other proposal here. 😁

@theunrepentantgeek
Copy link

What other languages support this?

I don't know about the modern versions, but Delphi supported this at least in versions 1-7 of its Object Pascal language. IIRC, the compiler enforced that exactly one inherited constructor call was made.

As I recall, being able to run code prior to invoking the constructor of the base class was immensely useful in some situations - though it's been so long that I'm fuzzy on the details. I do remember the C# style being very constraining when I was first picking up the language though ...

@HaloFour
Copy link
Contributor

@theunrepentantgeek

I don't know about the modern versions, but Delphi supported this at least in versions 1-7 of its Object Pascal language. IIRC, the compiler enforced that exactly one inherited constructor call was made.

Looks like that's still the case with Oxygene:

https://docs.elementscompiler.com/Oxygene/Members/Constructors/

@yaakov-h
Copy link
Member

Here's a real-world use-case I came across just last week:

using System;

namespace MyLibrary
{
    public partial interface IRuleInfo
    {
        public Guid Id { get; }
    }

    [Serializable]
    public class RuleValidationException : Exception
    {
        public RuleValidationException(IRuleInfo ruleInfo, Exception inner)
            : base(GenerateMessage(ruleInfo), inner)
        {
        }

        /* Additional constructors omitted */

        static string GenerateMessage(IRuleInfo ruleInfo = null)
        {
            return ruleInfo == null ? "Rule validation failed" : "Rule validation failed for Rule id: " + ruleInfo.Id;
        }
    }
}

It would be a lot nicer and easier to read if I could use the following constructor instead:

public RuleValidationException(IRuleInfo ruleInfo, Exception inner)
{
    if (ruleInfo == null)
    {
        base("Rule validation failed.", inner);
    }
    else
    {
        base("Rule validation failed for Rule id: " + ruleInfo.Id, inner);
    }
}

@juliusfriedman
Copy link

juliusfriedman commented Mar 31, 2019

@yaakov-h, I👏your efforts but find your first example better as it encapsulates OOP.

You can return an exception from a Validate method and not throw it too but that's rather heavy... why not just have a Validate which returns a State perhaps with a dictionary or otherwise...

The example you provided cannot be overcome by the base class changing the constructor which it calls and or another exception being thrown from that constructor anyway.

Also you can't really even guarantee that your constructor won't encounter the oom due to base classes logic so a Validate or Initialize method will beat this hands down evrtytime and doesnt require the effort to implement nor provide the issues which this may especially for structs.

@HaloFour
Copy link
Contributor

@juliusfriedman

find your first example better as it encapsulates OOP.

What does that have to do with OOP? That's like arguing that you can never override a member unless the very first thing you do in the overridden method is to call the base method. The fact that you can do other things before invoking the base method doesn't break anything about OOP. Heck, the fact that you're allowed to invoke the base method zero times, or a thousand times, doesn't break OOP. There's no tenet of OOP that requires that a base constructor call be the first "statement".

The example you provided cannot be overcome by the base class changing the constructor which it calls and or another exception being thrown from that constructor anyway.

That doesn't make sense. The derived class calls the base constructor in this example. If the base class was changed in that the constructor was no longer compatible that would break all code consuming the base class, irrespective of this proposal.

Also you can't really even guarantee that your constructor won't encounter the oom due to base classes logic so a Validate or Initialize method will beat this hands down evrtytime and doesnt require the effort to implement nor provide the issues which this may especially for structs.

Nobody is asking for such a guarantee. Nothing can guarantee that a field initializer won't throw, or that a static method called by the derived constructor within the base constructor call won't throw. Forcing the developer to move otherwise local validation and calculation code doesn't change any of that equation at all, except to break code locality and force additional boilerplate.

@juliusfriedman
Copy link

juliusfriedman commented Mar 31, 2019

@HaloFour

constructor which it calls, is what I said, all objects call a base constructor either ValueType or object or otherwise as required by the CLR.

The difference with encapsulation is overhead, I can choose and detect if I am validated either with Memoization or otherwise (caching).

It allows that portion of code to give it's own implementation if desired i.e. an IValidator which is a better encapsulation of multiple principles of OOP.

It also changes the state of a struct although I suppose you can already assign to this with structs in most cases and with classes through initialization and now that you mention static as well in the CLR due to state and guaranteed made about such which will NOT only be difficult to change now for everyone but can for that matter already be handled by the most determined either with a Host or Hijacking for better yet proper programming techniques which dont require us to do this in the first place.

Now for that matter I vote for this!

I'd in alot ways rather see this championed than default interface implementations...

Finally for that matter I would absokutely love to this feature on partial classes!

@yaakov-h
Copy link
Member

yaakov-h commented Apr 1, 2019

[scratches head]

I don't get how any of that is relevant at all...

@munael
Copy link
Author

munael commented Nov 13, 2019

Some restrictions that would allow some new constructs but might be easier to defend:

Example of allowed uses:

class Derived : Base {
    Derived(Foo foo) {
        // do bar and baz with foo
        base(bar, baz);
        //  blah
    }
}
  1. Exactly one base constructor call in the Derived constructor call is allowed.
  2. The base constructor call must have at least one argument (base(T)) (cannot defer a base() call).
  3. The base call must be in the outermost scope of the Derived constructor body. So, you cannot put it in branches or loops.
    a. How about try blocks?
    b. if/switch statements with an exhaustiveness check (all branches call the base at least once)?
    1. This's basically asking for linear typing for the base constructor, which is implicitly passed along to other calls. So, for example, you satisfy the requirement for the exactly-one call by calling a different derived class constructor that satisfies it.
  4. The base call is a statement, not an expression.

@KalleOlaviNiemitalo
Copy link

KalleOlaviNiemitalo commented Jan 1, 2021

Inspired by dotnet/runtime#46489, it could be handy if a constructor could handle exceptions thrown by the base class constructor, a bit like a function-try-block in C++.

partial class VolumeStream : FileStream
{
    public VolumeStream(string path)
    {
        SafeFileHandle handle = null;
        try
        {
            handle = OpenVolume(path);
            int sectorSize = GetSectorSize(handle);
            base(handle, FileAccess.ReadWrite, sectorSize);
        }
        catch
        {
            handle?.Dispose();
            throw;
        }
    }
}

Sequence expressions (#377) do not support try statements and so would not be able to handle these exceptions.

I don't see any restriction against catching base constructor exceptions in ECMA-335. III.1.8.1.4 (Class and object initialization rules) even has a note that mentions this possibility.

After catching, the derived constructor would not be allowed to return; it would have to throw something or loop infinitely. A constructor of a value type could perhaps be allowed to assign to this and return, but that does not seem worth supporting because the ValueType() constructor is unlikely to throw. I don't think the derived constructor should be allowed to retry the base constructor after an exception, because the base constructor may assume that instance fields have default values, and the previous attempt may have modified them already.

The syntax could be distinguished from base(T) (#2910) by checking whether the closing parenthesis is followed by a dot or by a semicolon.

@KalleOlaviNiemitalo
Copy link

The example above has the problem that one would not know whether base() is implicitly called at the top of the constructor, without reading the rest of the constructor. It would be spooky action at a distance, like yield return in iterator methods. Perhaps it would be better to require something like : explicit base after the parameter list in the constructor declaration, to indicate that the default : base() does not apply and the body will explicitly call a constructor.

@KalleOlaviNiemitalo
Copy link

This would help in dotnet/runtime#38804.

@shadow-cs
Copy link

shadow-cs commented Jun 14, 2021

A little late to the party but I offer another workaround, similar to the out arguments but a little more readable (to me at least) is using a constructor overload with a tuple:

  public class Base
  {
    protected Base(int a, string b, Guid c) { }
  }

  public class Derived : Base
  {
    public Derived(SomeType t) : this(PreInitialize(t))
    {
    }

    private Derived((int a, string b, Guid c) p) : base(p.a, p.b, p.c)
    {
      // other init here
    }

    private static (int a, string b, Guid c) PreInitialize(SomeType t)
    {
      if (t == null) throw new ArgumentNullException(nameof(t));
      int a = CalculateA(t);
      if (a < 0) throw new InvalidOperationException();
      string b = CalculateB(t, a);
      Guid c = CalculateC(t, a, b);
      return (a, b, c);
    }
  }

I'm using the same sample as @HaloFour.

Also using this technique gives you more freedom on what you can pass and what to do with values in the tuple (the overloaded ctor can also use them and store them in a field if needed which isn't possible without using the overload).

@bernd5
Copy link
Contributor

bernd5 commented Jun 14, 2021

@shadow-cs nice workaround

AFAIK the tuple solution would just not work for by-ref parameters and ref-like types (e.g. Span) - but for by value calls it is nice.

@333fred 333fred closed this as completed Jun 14, 2021
@dotnet dotnet locked and limited conversation to collaborators Jun 14, 2021

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
None yet
Development

No branches or pull requests