Allow calling base class constructor in the middle of derived class constructor #4827
Replies: 47 comments 14 replies
-
@narfanar
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. |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
Whilst a hacky workaround, you could use out parameters to avoid this wasted work. |
Beta Was this translation helpful? Give feedback.
-
@narfanar
|
Beta Was this translation helpful? Give feedback.
-
How would you out into the arguments of the base constructor? Can you provide an example?
Sorry? I'm not following. |
Beta Was this translation helpful? Give feedback.
-
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]);
}
} |
Beta Was this translation helpful? Give feedback.
This comment was marked as disruptive content.
This comment was marked as disruptive content.
-
@Korporal and then Cyrus addresses the reasons in literally the next line. Personal attacks are not appreciated here. |
Beta Was this translation helpful? Give feedback.
-
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 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 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. |
Beta Was this translation helpful? Give feedback.
-
I'm leaning towards the first option, but http://gafter.blogspot.com/2017/06/making-new-language-features-stand-out.html?m=1 |
Beta Was this translation helpful? Give feedback.
-
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 |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
Looks like that's still the case with Oxygene: https://docs.elementscompiler.com/Oxygene/Members/Constructors/ |
Beta Was this translation helpful? Give feedback.
-
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);
}
} |
Beta Was this translation helpful? Give feedback.
-
@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. |
Beta Was this translation helpful? Give feedback.
-
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".
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.
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. |
Beta Was this translation helpful? Give feedback.
-
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! |
Beta Was this translation helpful? Give feedback.
-
[scratches head] I don't get how any of that is relevant at all... |
Beta Was this translation helpful? Give feedback.
-
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
}
}
|
Beta Was this translation helpful? Give feedback.
-
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 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 The syntax could be distinguished from |
Beta Was this translation helpful? Give feedback.
-
The example above has the problem that one would not know whether |
Beta Was this translation helpful? Give feedback.
-
This would help in dotnet/runtime#38804. |
Beta Was this translation helpful? Give feedback.
-
A little late to the party but I offer another workaround, similar to the 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). |
Beta Was this translation helpful? Give feedback.
-
@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. |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
We all just talkin' in the wind again? Does talking here make a difference? How would one actually promote it into an idea the language heads could accept/reject? |
Beta Was this translation helpful? Give feedback.
-
Bit late - but I think the general idea is to have to same functionality as Java/PHP has with their constructors - that you, the programmer, can decide when the base constructor is called: PHP: https://www.php.net/manual/en/language.oop5.decon.php I also see some use to this - as it allows to transform an input argument for the constructor - or helps in clarity of the code even, as it's easier to read then some multilined The downside I see of the current workarounds (the out params and static function) - is that you it's freely to move around - so there's nothing preventing it from being moved anywhere except next to the constructor - by having the requested feature of being able to call |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
To allow
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?
Beta Was this translation helpful? Give feedback.
All reactions