How to override enum's "name" property? - dart

Minimal reproducible code:
enum Foo {
a,
b;
String get name {
switch (this) {
case Foo.a: return 'A';
case Foo.b: return 'B';
}
}
}
void main() {
printEnum<Foo>(Foo.values);
}
void printEnum<T extends Enum>(List<T> list) {
for (var e in list) {
print(e.name);
}
}
The for loop prints
a
b
But I wanted it to print
A
B
So, how do I override the name property in the enum?
Note:
Using (e as Foo).name will solve the issue, but I have many enums in my project, so I can't cast them like this.
Also, please don't post answers like, use toUpperCase(), etc, because I just provided a simple example, but in real world, things are quite different.

You cannot override the name getter "on Enum" because it's an extension getter, not an instance getter.
Overriding, aka. late binding, of instance members only apply to actual (virtual) instance members.
Extension members are statically resolved, so a call of .name either hits the extension member, or it doesn't, and it depends entirely on the static type of the receiver. If you have an Enum e; ... e.name ... then it will call the extension member, and there is absolutely no way to change that, or change what it does.
If you want to have a custom and overridable name getter, I'd introduce an interface like
abstract class NamedEnum extends Enum {
String get name;
}
and then let all your enums implement NamedEnum.
Then you can do (enumValue as NamedEnum).name for any of your enums.
It won't interact with other enum types' extension name getter.

Casting e as dynamic works, as long as you ensure that the name property exists on the enum you are printing. Ex:
void printEnum<T extends Enum>(List<T> list) {
for (var e in list) {
print((e as dynamic).name);
}
}

Related

How to use extended generics in Dart? [duplicate]

I'm trying to call a static method from a generic type I receive.
Is that even possible?
Furthermore, I apply a Type constraint in order to only manipulate the object from its parent class.
Here is a short example of what I'm trying to achieve:
class A {
static func() {
print("A");
}
}
class B extends A {
static func() {
print("B");
}
}
concret<T extends A>() {
T.func(); // I expected a print('B')
}
main() {
concret<B>();
}
No, it's not possible.
Dart static method invocations are resolved at compile-time, so it's not possible to call them on type variables which only have a value at run-time.
If it was possible, it would be completely unsafe. Anyone can create a class C extending A which does not have a static func member and invoke concret<C>();. Since static members are not inherited, it would have to give you a run-time error, and there is nothing you can do to detect that at compile-time. That is the primary reason why it is not allowed.

Is it possible to force a Type to be used only in static fields?

I'm working on a library, and I have a implementation pattern users are required to follow:
class MyView extends LibView {
static Foo f = Foo();
#override
void render(){
use(f); // f should be static, otherwise things not work correctly
}
}
I would like to tell the compiler that, if someone ever does this, it's incorrect:
class MyView {
Foo f = Foo(); // Error: Foo can only be used in Static field.
...
}
Anyone know if this is possible? I find it really hard to find good docs on these sorta of language details when it comes to dart.
[EDIT] Since the "why" question always comes up, imagine something like:
class ViewState{
Map<int, Object> props = {};
}
ViewState _state = ViewState();
class View {
View(this.state);
ViewState state;
static int _key1 = getRandomInt();
void render(){
print(state(_key1))
}
}
// These should both print the same value off of state since the 'random' int is cached
View(_state);
View(_state);
If the key's were not static, everything would compile fine, but they would not print the same results.
What you properly need are a singleton which can be created in different ways in Dart. One way is to use a factory constructor like this:
class Foo {
static final Foo _instance = Foo._();
factory Foo() => _instance;
// Private constructor only used internally
Foo._();
}
void main() {
final a = Foo();
final b = Foo();
print(identical(a, b)); // true
}
By doing it like this, there will only be one instance of Foo which are then shared each time an instance are asked for. The instance are also first created the first time it is asked for since static variables in Dart are lazy and only initialized when needed.
I just want to do the functional equivalent of
int someUniqueKey = 0, or MyViewEnums.someUniqueKey but do it with a typed object rather than a int/enym, like: Object<Foo> someUniqueKey = Object<Foo>(). In order for this to work with Objects, it needs to be static. It's similar to how int someUniqueKey = random.nextInt(9999) would have to be static in order to be used as a key that all instances could share. That way keys are auto-managed and unique, and people don't need to assign int's, strings, or whatever. It also has the advantage of letting me use the type later for compile time checks.
bool prop = getPropFromRef(_prop1Ref); //Will throw error prop1Ref is not Ref<bool>
I think I've figured out something that does the trick using darts package-level methods.
class Ref<T> {}
// Re-use existing ref if it already exists
Ref<T> getRef<T>(Ref<T> o) => o ?? Ref<T>();
class RefView {}
// In some other package/file:
class MyView extends RefView {
static Ref<bool> prop1Ref = getRef(prop1Ref);
static Ref<int> prop2Ref = getRef(prop2Ref);
}
This will make sure that prop1 and prop2 have the same values across all instances of MyView and it will throw an error if these are not static (since you can not pass an instance field before Constructor)
This still has the downside of a potential hard to spot error:
class MyView extends RefView {
static Ref<bool> prop1 = getRef(prop1);
static Ref<bool> prop2 = getRef(prop1); // passing prop1 to prop2's getRef, and they have the same<T>, compiler will miss it
}
But I think it might be preferable than having this potential error:
class MyView extends RefView {
//Both of these will fail silently, keys will change for each instance of MyView
Ref<bool> prop1 = getRef(prop1);
Ref<bool> prop2 = getRef(prop2);
}

Dart abstract optional parameters

How can I abstract that a methods has optional parameters?
abstract class CopyWith<T>{
T copyWith({}); // Error : Expected an identifier.
}
If I add an identifier like {test} it works and subclasses can have additional arguments
What I want to achieve?
I have a complex state manager, I make some abstraction , the following code is a minimal code, show my problem
import 'dart:collection';
abstract class CopyWith<T> {
T copyWith(OPTIONAL_NAMED_ARGUMENTS);
}
abstract class Manager<K, V extends CopyWith> {
final _map = HashMap<K, V>();
add(K key,V value){
_map[key] = value;
}
void copyWith(K key,OPTIONAL_NAMED_ARGUMENTS) {
assert(key != null);
if (_map.containsKey(key)) {
_map[key].copyWith(OPTIONAL_NAMED_ARGUMENTS);
}
}
}
class User implements CopyWith {
final int id;
final String name;
User({this.id, this.name});
User copyWith({int id, String name}) {
return User(
id: id ?? this.id,
name: name ?? this.name,
);
}
}
class UserManager extends Manager<int, User> {}
void main() {
final userManager = UserManager();
userManager.add(1,User(1,'test'));
userManager.copyWith(1,{test:'test2'})
}
As some one who has faced this issue in my library, I would say the only way is to not put a copyWith in your base class.
Why? Because you should only make a function polymorphic when there IS actually a shared calling convention and behavior. In your example, The way that these two classes perform copyWith is just different. It is, and should be, an error to send a name to Manager.copyWith, because Manager does not have a name to begin with. If you encounter a name inside a Manager.copyWith, that means there is some serious error in your code.
Also, if you actually try to invoke copyWith, as a responsible programmer, you will probably check if you are allowed to pass a name, which is,
if (someObj is User) {
someObj.copyWith(key, name: name);
} else if (someObj is Manager) {
throw IllegalStateError('You should not pass a name to a Manager! What am I supposed to do with the name now?');
}
There, you have already done type checking, so no need to make copyWith polymorphic.
However, some common behaviors can be made polymorphic, like updateKey. You can make Keyable as an interface, and Keyable updateKey(Key key) as an abstract method, and delegate to a non-polymorphic copyWith inside each subclasses.

One extension for several types

In project we have a lot of enum that need to be serializable and
deserializable in/from String. So I wrote the extension:
extension EnumSerializer on MyEnum {
String getString() => this.toString().split('.').last;
static MyEnum fromString(String str) => MyEnum.values.firstWhere( (v) => v.getString() == str, orElse: () => throw SerializerError('bad string');
}
But problem is I should write new extension with the same content for every enum.
Is it possible to do something like this:
extension EnumSerializer on MyEnum, OtherEnum, AnotherEnum { ...
As a direct answer to your question, no, that is not possible.
As a side note, the lack of native parsing functionality for enums is a sticking point for a lot of people. The extension approach helps, but it's hampered by both the lack of a base class for enum types (forcing an extension be made for every enum type) and the lack of support for static type extensions.
I would argue that it's saner to instead forgo extensions altogether and create a generic static utility method somewhere that can handle all enum types:
class EnumHelper {
static String asString<T>(T value) {
final str = value.toString();
return str.substring(str.indexOf('.') + 1);
}
static T parse<T>(String str, List<T> values) {
for (var v in values) {
if (toString(v) == str) {
return v;
}
}
return null;
}
}
// Usage
enum Foo { a, b }
EnumHelper.asString(Foo.a); // "a"
EnumHelper.parse("a", Foo.values); // Foo.a
EDIT: As of Dart 2.15, you no longer need extension methods or custom utilities for this functionality. Enums have builtin helper methods for parsing and serializing:
enum Foo { a, b }
Foo.a.name; // "a"
Foo.values.byName("a"); // Foo.a
Foo.values.asNameMap(); // { "a": Foo.a, "b": Foo.b }
I've created feature request in dart repository:
It's much more likely that we'll introduce a supertype of enums, than that we will allow an extension on multiple types.
The latter would mean that this inside the extension cannot be typed, which is a no-go in typed language like Dart 2.
One alternative is that If we had union types, you could do on (Type1 | Type2 | Type3) and let that union type be the type of this inside the extension. We don't have union types yet, and likely won't any time soon, it'd be a large change to the Dart type system.
So, a "no" to multiple extension on types from me. You have to find a common supertype for them.

How do I compare two objects to see if they are the same instance, in Dart?

Say I have a class that has many instance variables,. I want to overload the == operator (and hashCode) so I can use instances as keys in maps.
class Foo {
int a;
int b;
SomeClass c;
SomeOtherClass d;
// etc.
bool operator==(Foo other) {
// Long calculation involving a, b, c, d etc.
}
}
The comparison calculation may be expensive, so I want to check if other is the same instance as this before making that calculation.
How do I invoke the == operator provided by the Object class to do this ?
You're looking for "identical", which will check if 2 instances are the same.
identical(this, other);
A more detailed example?
class Person {
String ssn;
String name;
Person(this.ssn, this.name);
// Define that two persons are equal if their SSNs are equal
bool operator ==(Person other) {
return (other.ssn == ssn);
}
}
main() {
var bob = new Person('111', 'Bob');
var robert = new Person('111', 'Robert');
print(bob == robert); // true
print(identical(bob, robert)); // false, because these are two different instances
}
You can use identical(this, other).
For completeness, this is a supplemental answer to the existing answers.
If some class Foo does not override ==, then the default implementation is to return whether they are the same object. The documentation states:
The default behavior for all Objects is to return true if and only if this object and other are the same object.
That's my way how I compare deep 2 Objects they're not the same:
class Foo{
String uid;
bool isActiv;
Foo(this.uid, this.isActiv){}
Map<String, dynamic> toJson() => _$FooToJson(this);
}
Foo A = Foo("alpha", true);
Foo B = Foo("alpha", true);
print(A.toJson().toString() == B.toJson().toString()); // true
B.uid = "beta";
print(A.toJson().toString() == B.toJson().toString()); // false
On a different yet similar note, in cases where the framework calls to check the equality among the objects e.g. in case of list.toSet() to get the unique elements from a list, identical(this, other) may not be a choice. That time the class must override the == operator and the hasCode() methods.
However for this case another way could be to use the equatable package. This saves a lot of boiler plate code and is especially handy when you have lot of model classes.
You can use Equatable library
class Foo extends EquatableMixin{
int? a;
int? b;
SomeClass? c;
SomeOtherClass? d;
Foo(this.a,this.b,this.c,this.d);
// this does the job, it overrides the hashcode and equals operator
// give all properties to this `props`
#override
List<Object> get props => [a,b,c,d];
}
class SomeOtherClass with EquatableMixin{
String name;
SomeOtherClass(this.name);
#override
List<Object> get props => [name];
}
class SomeClass with EquatableMixin{
String name;
SomeClass(this.name);
#override
List<Object> get props => [name];
}
Foo foo =
Foo(1,2,SomeOtherClass("roger"),SomeOtherClassObject("mack"));
Foo foo2 =
Foo(1,2,SomeOtherClass("roger"),SomeOtherClassObject("mack"));
print(foo == foo2) // prints true
So, we don't need to manually override == and hashcode() methods
the library will do that.
Note : the inner objects (SomeClass and SomeOtherClass) should also use EquatableMixin, we can extends this or use as a mixin too

Resources