I have the following variable and getter / setter defined in my data model:
class Actor {
int _x;
int get x => _x;
set x(int value) => _x = value;
}
And there is this generic class that requires a getter / setter function pointer
class PropertyItem {
var getterFunction;
var setterFunction;
PropertyItem(this.getterFunction, this.setterFunction);
}
How do i pass a reference of the getter / setter function of X to the PropertyItem class?
// Something like this
var item = new PropertyItem(x.getter, x.setter);
EDIT: Updated with a more clear question
In short, you don't.
Getters and setters are not extractable - they are indistinguishable from just having a field (if you don't do side-effects, of course).
In your example, you could just do:
class Actor {
int x;
}
and get exactly the same effect.
What you want is, for some Actor "actor", to make the functions yourself:
var item = new PropertyItem(() => actor.x, (v) { actor.x = v; });
This proposal about generalized tear offs is approved and will probably implemented soon and allows to closurize getters and setters like:
var item = new PropertyItem(actor#x, actor#x=);
In Dart, the following:
class Foo {
int _offsetX;
int get offsetX => _offsetX;
set offsetX(int ox) => _offsetX = ox;
}
is equivalent to:
class Foo {
int offsetX;
}
Related
I am passing a Function as an optional parameter to the constructor but I can't assign a default value.
void main() {
Person p = Person();
print(p.foo('Hello'));
}
class Person {
final String Function(String) foo;
Person({this.foo});
}
now trying to assign a default value: Person({this.foo = (val) {return val;});
produces the error: Error: Not a constant expression. I am aware the parameter must be const but using const or even static infront of (val) {return val;} does not work.
Does anyone have an idea how to solve this problem?
You can try this:
void main() {
Person pLower = Person(foo: (a) => a.toLowerCase());
print(pLower.foo('Hello'));
Person pDefault = Person();
print(pDefault.foo('Hello'));
}
class Person {
static String defaultFoo(String a) => a.toUpperCase();
final String Function(String) foo;
Person({this.foo = defaultFoo});
}
Output
hello
HELLO
You can only use constant values (aka. compile-time constants) as default values.
You cannot create a constant function literal, so there is no way to write the function in-line in the constructor.
However, references to top-level or static functions are constants, so you can declare the default value function as a static function or top-level function.
void main() {
Person p = Person();
print(p.foo('Hello')); // Prints "Hello"
}
class Person {
final String Function(String) foo;
Person({this.foo = _identity});
static String _identity(String value) => value;
}
// or as top-level.
// String _identity(String value) => value;
You can (and should) choose to make the function public if the default value is on an instance method, and you expect anyone to extend or implement your class. In that case, they need to declare the same default value.
Another option, which is often at least as useful, is to not use a default value, but replace a null before using the value:
class Person {
final String Function(String) foo;
Person({String Function(String) foo}) : foo = foo ?? _identity;
static String _identity(String value) => value;
}
or even using a non-constant value:
class Person {
final String Function(String) foo;
Person({String Function(String) foo}) : foo = (foo ?? (String x) => x);
}
For a constructor, it makes very little difference. If it was an instance method instead, using ?? to replace null avoids subclasses having to use the exact same function as default value.
Personally I recommend always using ?? instead of a default value. It's more flexible since it allows non-constant values. For non-function default values, you'll have to document the default behavior instead of just letting the dartDoc show {int x = 42}, but for functions, you'll have to document them anyway.
Consider this class that is used as a data model in a Model-View-Controller scenario (I'm using TypeScript 3.5):
export class ViewSource {
private viewName : string;
private viewStruct : IViewStruct;
private rows : any[];
private rowIndex : number|null;
constructor(viewName : string) {
// Same as this.setViewName(viewName);
this.viewName = viewName;
this.viewStruct = api.meta.get_view_struct(viewName);
if (!this.viewStruct) {
throw new Error("Clould not load structure for view, name=" + (viewName));
}
this.rows = [];
this.rowIndex = null;
}
public setViewName = (viewName: string) => {
this.viewName = viewName;
this.viewStruct = api.meta.get_view_struct(viewName);
if (!this.viewStruct) {
throw new Error("Clould not load structure for view, name=" + (viewName));
}
this.rows = [];
this.rowIndex = null;
}
public getViewStruct = ():IViewStruct => { return this.viewStruct; }
public getCellValue = (rowIndex: number, columnName: string) : any => {
const row = this.rows[rowIndex] as any;
return row[columnName];
}
}
This is not a complete class, I only included a few methods to demonstrate the problem. ViewSource is a mutable object. It can be referenced from multiple parts of the application. (Please note that being a mutable object is a fact. This question is not about choosing a different data model that uses immutable objects.)
Whenever I want to change the state of a ViewSource object, I call its setViewName method. It does work, but it is also very clumsy. Every line of code in the constructor is repeated in the setViewName method.
Of course, it is not possible to use this constructor:
constructor(viewName : string) {
this.setViewName(viewName);
}
because that results in TS2564 error:
Property 'viewStruct' has no initializer and is not definitely assigned in the constructor.ts(2564)
I do not want to ignore TS2564 errors in general. But I also do not want to repeat all attribute initializations. I have some other classes with even more properties (>10), and the corresponding code duplication looks ugly, and it is error prone. (I might forget that some things have to bee modified in two methods...)
So how can I avoid duplicating many lines of code?
I think the best method to avoid code duplication in this case would be to create a function that contains the initialization code, but instead of setting the value, it retunrs the value that need to be set.
Something like the following:
export class ViewSource {
private viewName : string;
private viewStruct : IViewStruct;
private rows : any[];
private rowIndex : number|null;
constructor(viewName : string) {
const {newViewName, newViewStruct, newRows, newRowIndex} = this.getNewValues(viewName);
this.viewName = newViewName;
this.newViewStruct = newViewStruct;
// Rest of initialization goes here
}
public setViewName = (viewName: string) => {
const {newViewName, newViewStruct, newRows, newRowIndex} = this.getNewValues(viewName);
// Rest of initialization goes here
}
privat getNewValues = (viewName) => {
const newViewName = viewName;
const newViewStruct = api.meta.get_view_struct(viewName);
if (!newViewStruct) {
throw new Error("Clould not load structure for view, name=" + (viewName));
}
const newRows = [];
const newRowIndex = null;
return {newViewName, newViewStruct, newRows, newRowIndex};
}
}
This way the only thing you duplicate is setting the values, not calculating them, and if the values calculations will get more complicated you can simply expand the returned value.
A less complex approach than the accepted answer is to use the //#ts-ignore[1] comment above each member that is initialized elsewhere.
Consider this contrived example
class Foo {
// #ts-ignore TS2564 - initialized in the init method
a: number;
// #ts-ignore TS2564 - initialized in the init method
b: string;
// #ts-ignore TS2564 - initialized in the init method
c: number;
constructor(a: number, b: string) {
if(a === 0) {
this.init(a,b,100);
} else {
this.init(a,b,4912);
}
}
private init(a: number, b: string, c: number): void {
this.a = a;
this.b = b;
this.c = c;
}
}
Since TypeScript 3.9 there exists the //#ts-expect-error[2] comment, but I think #ts-ignore is suitable.
[1] Suppress errors in .ts files
[2] TS expect errors comment
Since TypeScript 2.7 you can use the definite assignment assertion modifier which means adding an exclamation mark between the variable name and the colon:
private viewName!: string
This has the same effect as adding a // #ts-ignore TS2564 comment above it as #RamblinRose suggested.
I can conveniently change opsCount variable directly from inside the function,
because there is only one of that type of variable.
int opsCount = 0;
int jobXCount = 0;
int jobYCount = 0;
int jobZCount = 0;
void doStats(var jobCount) {
opsCount++;
jobCount++;
}
main() {
doStats(jobXCount);
}
But there are many jobCount variables, so how can I change effectively that variable, which is used in parameter, when function is called?
I think I know what you are asking. Unfortunately, the answer is "you can't do this unless you are willing to wrap your integers". Numbers are immutable objects, you can't change their value. Even though Dart's numbers are objects, and they are passed by reference, their intrinsic value can't be changed.
See also Is there a way to pass a primitive parameter by reference in Dart?
You can wrap the variables, then you can pass them as reference:
class IntRef {
IntRef(this.val);
int val;
#override
String toString() => val.toString();
}
IntRef opsCount = new IntRef(0);
IntRef jobXCount = new IntRef(0);
IntRef jobYCount = new IntRef(0);
IntRef jobZCount = new IntRef(0);
void doStats(var jobCount) {
opsCount.val++;
jobCount.val++;
}
main() {
doStats(jobXCount);
print('opsCount: $opsCount; jobXCount: $jobXCount; jobYCount: $jobYCount; jobZCount: $jobZCount');
}
EDIT
According to Roberts comment ..
With a custom operator this would look like:
class IntRef {
IntRef(this.val);
int val;
#override
String toString() => val.toString();
operator +(int other) {
val += other;
return this;
}
}
void doStats(var jobCount) {
opsCount++;
jobCount++;
}
In the example below I was hoping sum getter would return 8, but it is a compile error.
Class 'B' has no instance getter 'sum'.
According to the spec:
Using an abstract class instead of an interface has important
advantages. An abstract class can provide default implementations; it
can also provide static methods, obviating the need for service
classes such as Collections or Lists, whose entire purpose is to group
utilities related to a given type.
What is the correct way to provide a default implementation of sum that adds x and y?
abstract class A {
int get x;
int get y;
int get sum => x+y;
}
class B implements A {
int get x => 3;
int get y => 5;
}
main() {
B b = new B();
print(b.x);
print(b.sum); // Not working, why not 8?
}
You have to make B extend A instead of implement.
abstract class A {
int get x;
int get y;
int get sum => x+y;
}
class B extends A {
int get x => 3;
int get y => 5;
}
main() {
B b = new B();
print(b.x);
print(b.sum); // displays 8
}
Alternatively if you don't want to use extends because your class may already extend an other class, you can use mixins :
abstract class M {
int get x;
int get y;
int get sum => x+y;
}
class A {
String s = "s";
}
class B extends A with M {
int get x => 3;
int get y => 5;
}
main() {
B b = new B();
print(b.s);
print(b.x);
print(b.sum); // displays 8
}
Another way around this issue is to just use extensions (depending on your use case). This way all your default method implementations will work regardless if you extend, implement, mixin, ect.
abstract class A {
int get x;
int get y;
}
class B implements A {
int get x => 3;
int get y => 5;
}
extension E on A {
int get sum => x+y;
}
main() {
B b = new B();
print(b.x);
print(b.sum); // 8
}
By choosing to implement A, you have to implement everything A requires even if you have provided default implementations. If you want to use default implementation from A while having the flexibility to provide your own implementations you have to use A as a mixin:
class B with A {
int get x => 3;
int get y => 5;
}
If I have a class with a setter defined, how do I reference then generated method as a function from an instance of that class. The spec sort of suggests it would be the id of the variable + '=" (seems daft), but this doesn't parse.
So for example:
class Bar {
set foo(int value) {
//whatever
}
}
typedef F(int value);
void main() {
F f = new Bar().foo=; //Fails, but what should this be??
f(5);
}
The setter is named foo= but this is not something you can reference in the way you want. Even looking at dart:mirrors the MethodMirror (the mirror for object methods including setters) has no way of invoking it. You could easily rewrite this as:
class Bar {
set foo(int value) {
//whatever
}
}
typedef F(int value);
void main() {
Bar b = new Bar();
F f = (int value) => b.foo = value;
f(5);
}