language
language copied to clipboard
Partial Classes and Methods feature request
Would like to have something similar in a dart. Especially when generating code. Example: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/partial-classes-and-methods
Possibly, covered by #177
Right, scoped class extension (#177) is intended to allow third-party developers (that is, for a class C, developers who won't edit the code of C because they can't or because it's somebody else's code) to add proper instance methods to an existing type hierarchy (a typical example would be a tree of classes that extend each other), with the ability to inherit and/or override method implementations, and the ability at call sites to have all the usual typing properties (e.g., that a method may accept more optional arguments than the methods that it overrides).
For code generation you might be able to decide up front that you can always edit the code of the target class(es), in which case you might be able to use a mechanism based on part files.
Such a mechanism would allow you to patch a class C in the main file by adding method implementations and (possibly private) declarations from the part file in a "partial" declaration of C, and it could basically be considered to be similar to an include file mechanism (the effective source code in the main file is obtained by adding stuff from the part files).
This is simpler than scoped class extension, but it will only work in cases where you have enough control over the target libraries to, at least, be able to introduce those part declarations into the target libraries and generate the part files.
Also, you can control scoped class extension because it is scoped. So if two third-party organizations wish to add a method named foo to the same class(es) then a part based approach will just incur some "duplicate declaration" errors at compile-time. But with scoped class extensions you'd make the choice in each library that contains a call site that you import just one of those conflicting extensions, which eliminates the conflict.
A common pattern to generate code (with source_gen):
// file.dart
part 'file.gen.dart';
class A extends $A {
}
// file.gen.dart
part of 'file.dart';
class $A {
myAwesoneGeneration() => null;
}
With this pattern it's not possible/simple to generate constructors or static member.
Right, and it's also not possible to add a method to num, int, and double.
C# partial classes do not allow you to add members to existing non-partial classes.
You can only have multiple parts of a class declared to be partial, and still only in the same assembly (compilation unit). In Dart, I'd say it would have to be the same library.
With the weird behavior of extensions methods tear-off that are never equal to themselves https://github.com/dart-lang/language/issues/425, I think partial classes are important.
Without partial classes, code-generators will instead generate extension members. But this could be very "dangerous" because of the tear-off issue.
This would be so helpful.
I'm following up with code-generation (#1482) and the question of where to put the code is starting to be an issue for me. I've experimented with extension, mixins, extends, external, and part, and I can't figure out a good convention that works in all cases. To demonstrate why, consider the simple and commonly-requested example of an AutoDispose code-gen, which automatically calls dispose on fields marked with shouldDispose in the following class:
// my_widget.dart
part "my_widget.g.dart";
@AutoDispose()
class MyState extends State<MyWidget> {
/// This annotation is defined by my macro. It tells it to include this field in [dispose].
@shouldDispose
final TextEditingController controller;
@override
Widget build(BuildContext context) => Scaffold(
// ...
);
}
I tried extensions, which doesn't work because you can't override members with extensions.
// my_widget.g.dart
part of "my_widget.dart";
extension on MyState {
@override // INFO: The method doesn't override an inherited method.
void dispose() { // INFO: The declaration 'dispose' isn't referenced
controller.dispose();
super.dispose();
}
}
I tried mixins, but because I want to declare the mixin on MyState, it gets ugly:
// my_widget.dart
part "my_widget.g.dart";
// The following is nice, but does not work:
class MyState with GeneratedMixin { }
// Error: 'MyState' can't be a superinterface of itself: MyState, GeneratedMixin, MyState.
mixin GeneratedMixin on MyState { }
// Error: 'GeneratedMixin' can't be a superinterface of itself: GeneratedMixin, MyState, GeneratedMixin.
// So I had to resort to this
class TempState extends State<MyWidget> { /* the body of MyState */ }
class MyState = TempState with GeneratedMixin;
// my_widget.g.dart
part of "my_widget.dart";
mixin GeneratedMixin on TempState {
@override
void dispose() {
controller.dispose();
super.dispose();
}
}
I initially played with extensions, but partial classes fit better here. That's because extensions have three key limitations:
- Extensions cannot define constructors
- Extensions cannot override members. Particularly not members of
Object. - Extensions cannot define fields
Partial classes fix all three of these, and allow generated code to live happily side-by-side with human code, while allowing complete separation. Broadly, both part/part of and extension have a common goal: to split code across declarations/files. Partial classes will fill in the gaps.
PS: I'm in favor of partial class X instead of class X partial. It fits better with mixin, abstract, extension, and var which describe the declaration itself instead of implements, extends, and on, which only slightly modify the declaration (and thus go after).
My proposal for code generation is at #1565. Please take a look, I believe it fully utilizes the potential of partial classes. It's also dependent on this issue, so add my vote to the count!
This would be really useful, is it on Dart team's roadmap ?
Not on the roadmap currently, no.
Any updates on this?
Check out the "augmentation" feature.
Partial classes are definitely the way to go. Seems like a natural evolution of the language. Extensions are great for what they are, but we have some very large classes that now must fit within a single source file so that private variables can be accessed. This is making code maintenance more difficult than it needs to be. And before someone says we should refactor our code, this is server side code and a single class encapsulates a single topic of concern. That topic can have many functions to it that make the topic cohesive and complete.
Please add this feature to the near-term road map. Thanks!
Was this implemented?
Was this implemented?
No.
Maybe this is partially or fully solved by class augmentations, but they were not released yet.
@mateusfccp is correct. We haven't shipped anything yet, but are actively working on augmentations which is cover this and many more use cases.