-
Notifications
You must be signed in to change notification settings - Fork 1k
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
Comments
@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. |
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. |
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. |
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. |
Whilst a hacky workaround, you could use out parameters to avoid this wasted work. |
@narfanar
|
How would you out into the arguments of the base constructor? Can you provide an example?
Sorry? I'm not following. |
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]);
}
} |
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. |
@Korporal and then Cyrus addresses the reasons in literally the next line. Personal attacks are not appreciated here. |
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. |
I'm leaning towards the first option, but http://gafter.blogspot.com/2017/06/making-new-language-features-stand-out.html?m=1 |
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 |
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. |
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.
And i would not. Because it's important to recognize the intentional decisions from the unintentional ones especially in the domain of language design. |
To argue against myself it's also just as easy to use a 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;
}
} |
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. |
The C# compiler does this all the time, in that you're allowed to have expressions in the |
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 |
Why should the base class be at all aware of what a derived constructor might want to do?
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.
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 |
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... |
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. |
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. |
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.
Nobody is talking about properties. I'm talking explicitly and exclusively about the parameters to the derived constructor.
Again, I'm not talking about validating the object which you are trying to construct before you've constructed it. |
This is indeed the only tricky aspect about a feature like this. The compiler would have to enforce that you invoke the |
@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. |
There is no chaos here. You're reading something into this proposal that isn't there.
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. |
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 |
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. |
That adds more complexity and moves the logic away from where it is relevant.
It doesn't divorce the construction process, it only enables more code constructs to exist before the base constructor call.
D does. Check out 15.8.9 of the D spec. In particular:
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.
Sure, I'm not going to argue against that point. Of course it also applies to literally every other proposal here. 😁 |
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 ... |
Looks like that's still the case with Oxygene: https://docs.elementscompiler.com/Oxygene/Members/Constructors/ |
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);
}
} |
@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. |
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. |
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! |
[scratches head] I don't get how any of that is relevant at all... |
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
}
}
|
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 |
The example above has the problem that one would not know whether |
This would help in dotnet/runtime#38804. |
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). |
@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. |
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
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?
The text was updated successfully, but these errors were encountered: