-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
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
In Avalonia.Base
, sealed several classes whose constructors aren't available from public API, and which have no inheritors
#13043
Conversation
…available from public API, and which have no inheritors
You can test this PR using the following package version. |
How would it make no difference? Apps may be using base classes in several places you have now sealed. I really wish you would at least open a discussion before going through and making these types of PRs... I was one that was strongly stating we should allow these types of changes but you are doing some dangerous stuff. Personally, I am not a fan of sealing too much. In fact I wouldn't accept this PR if it was up to me. If there are some performance critical paths that could be another discussion but I don't think that is how you approached this. |
Hi @robloo, if the constructors in question are |
Yes, I suppose you're right from that standpoint. Why do classes like CubicBezierEasing having an internal constructor to begin with though? Seems plausible to derive from that some new easing in an application. Why would that even be locked down? Doesn't make sense to me. That can be said a few other places as well. Sealing classes isn't simply a "well it isn't used so let's lock it down" type of discussion either. Is the class intended to be derivable? Is deriving something that should be supported from an API standpoint? That is an important question to answer before sealing anything and it's a question the core team or original author needs to answer in a broad sense. |
I understand where you are coming from. To address your concern, let me ask you a question. What should the default behavior (sealed vs. unsealed) be, and why? In the C# language, classes are unsealed by default (imo an unfortunate design choice); but should the default behavior of the language determine your OOP design choices? If the designers of the language had determined that classes were to be sealed by default (which is the case of Kotlin, for example), would you say the opposite? (I am presuming that this is your line of thought, but correct me if I am wrong). As you may guess, my choice is 'sealed by default'. If our roles were reversed, I would say "unsealing classes isn't simply a 'well, someone might want to inherit from it so let's open it up' type of discussion either. Is the class intended to be derivable? Is deriving something that should be supported from an API standpoint? That is an important question to answer before unsealing anything and it's a question the core team or original author needs to answer in a broad sense." Why do I lean towards sealed by default? In short, ensuring as many invariants as possible. It's like the I remember a pull request from some time ago where I made certain private methods Sorry if my pull request feels spammy (it may seem like that since, admittedly, my explanation for the PR was too short), or that I didn't give it enough thought; but hopefully you can see that there is a reasoning behind it. |
CI script should validate API surface using .NET SDK validators. Even though it is known to have some issues.
Our easing algorithms/classes are not extendable in this way. You can, though, implement your own IEasing type. Or use configurable SplineEasing and SpringEasing.
It's a good point. Especially coming from UWP, where everything is sealed by default (IIRC, WinRT IDL by itself seals everything by default). |
To focus on Since it's already public and can be useful, I believe it should be modified to be usable publicly, like other easings:
With 1-3 it's at least usable explicitly from C# and XAML, but not as a string, which requires point 4. All that is completely out of scope of this PR. For now, I'd say to leave the class unsealed. |
You can test this PR using the following package version. |
Setters doing nothing look like an oversight/bug. That class should lazily create the |
Actually, it also seems to be duplicating the KeySpline thingy, except CubicBezier class is a line-by-line port from Chrome. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am going to merge this PR, as it doesn't break any API. It wasn't possible to inherit CubicBezier before or after this PR.
Please consider creating an issue or another PR changing this easing class independently from this PR.
Well, I do hope someone on the core team went through each and it makes sense to seal the types that were sealed from an API standpoint. There was at least one other:
I didn't want to get into the weeds with the discussion. But you answered your own question -- unsealed is the default in C# and is most widely expected for app developers. Avalonia is also most useful because it is extensible and customizable. In 11.0 however a lot of that power has been lost and I'm not sure its for the best. I would NOT go around sealing things unless you know from a design standpoint it shouldn't be derived. That means while in this case this PR doesn't change the API surface area I would not make a habit of sealing things. It locks things down too much for app developers and is usually unnecessary. |
Internal and private classes are better sealed in .NET (Core), since the JIT can optimize them (for example Unsealed by default is considered by some C# team members a mistake, I've seen it mentioned a couple of times in the C# language design repository. (Sorry, I can't find a relevant discussion right now, it's probably buried somewhere in an issue's comments.) Here's an old SO answer by Jon Skeet about it. Of course this position isn't universal and is highly debatable. An unsealed class should be designed with inheritance in mind. Most often, unsealed without virtual is pretty useless: simply leaving a class unsealed won't make it easily replaceable by an inherited type if the system hasn't been designed for it. Sealed public classes can be unsealed later if necessary, whereas the opposite can't be done without making breaking changes. That being said, I personally do agree that some things were made less extensible a bit too harshly, as I've commented before. My concerns are mostly about "pubternal" vs |
What does the pull request do?
Helps track which classes aren't inherited from, at least internally
What is the updated/expected behavior with this PR?
No behavior changes
Breaking changes
If there is an analysis tool that checks for breaking changes in public API, it may complain, but it really makes no difference