Is there any way to get an object by it's UID so that the following code would work?
When the function finishes, the value of property "xxx" should be "string two" not "string one".
// Test class
public function test():void {
this.xxx = "string one";
foo.bar(this.xxx);
trace(this.xxx); // Prints: string two
}
// Foo class
public function bar(value:*):void {
// ... What would I have to do here to get the property, not its value?
value = "string two";
}
What about using the Box brackets? I know this is not the OO way of doing the thing but Action script supports it and it looks like a good alternative here.
class Test {
public var xxx:String;
public function test():void {
this.xxx = "string one";
foo.bar(this,"xxx"); // actual name of property as string ;litral
trace(this.xxx); // Prints: string two
}
}
class Foo {
public function bar(test:*,prop:String):void {
//test could be from any class .
test[prop] = "string two";
}
}
This should do the trick. But You need to make sure whichever code calls "bar" method passes a valid object which has "xxx" property defined as this code is not type safe any more.
A function's parameter (a reference to a variable) can't be changed. It's not a pointer. You can assign other variables to it, but it won't change the argument passed to that function. But you can change the argument's properties:
class Test {
public var xxx:String;
public function test():void {
this.xxx = "string one";
foo.bar(this);
trace(this.xxx); // Prints: string two
}
}
class Foo {
public function bar(test:Test):void {
test.xxx = "string two";
}
}
Of course for this to work, the class Foo has to know Test and also which property to change. This makes everything less dynamic and is maybe not what you want. It's a case where you could use an Interface. Or you might want to stick to common patterns like using a getter and assigning the value to the appropriate property:
class Test {
public var xxx:String;
public function test():void {
this.xxx = "string one";
this.xxx = foo.getValue();
trace(this.xxx); // Prints: string two
}
}
class Foo {
public function getValue():String{
return "string two";
}
}
To get to a property, the easiest way is to encapsulate the property into an object, pass it to the function, and then retrieve it:
// Test class
public function test():void {
var obj: Object = new Object();
obj.variable = "string one";
foo.bar(obj);
trace(obj.variable); // Prints: string two
}
// Foo class
public function bar(value:Object):void {
value.variable = "string two";
}
But why would you want to do this? It's much better in every way to just do xxx = foo.bar();
Pass-by-value:
When passing a variable into a function the variable is copied. Any changes you make to the variable aren't reflected back once you exit.
Pass-by-reference:
When passing a variable into a function, the "pointer" to the variable is passed. Any changes you make to the variable are copied over.
In AS3, everything is pass-by-reference, except primitives (Boolean, String, int, uint, etc), which have special operators behind the scenes to make them act like pass-by-value. As xxx is a String, this is what's happening. (Also, Strings are immutable; you can't actually change their value).
How to fix it (as others have said):
Pass the Test object itself to the bar() function: bar( this );
Encapsulate the xxx parameter in it's own object and pass that: bar( {prop:this.xxx} );
Have bar() return the value and set it: this.xxx = bar();
Related
In Haxe, I created a class named MyClass like:
class MyClass {
var score: String;
public function new (score: Int) {
this.score = Std.string(score);
}
public function new (score: String) {
this.score = score;
}
}
I need multiple constructors but Haxe does not allow me to do. It throws this error from building phase:
*.hx:*: lines * : Duplicate constructor
The terminal process terminated with exit code: 1
How can I solve this problem?
This is known as method overloading, which is not supported by Haxe apart from externs (but might be in the future). There's multiple ways you could work around this.
A common workaround in the case of constructors would be to have a static "factory method" for the second constructor:
class MyClass {
var score:String;
public function new(score:String) {
this.score = score;
}
public static function fromInt(score:Int):MyClass {
return new MyClass(Std.string(score));
}
}
You could also have a single constructor that accepts both kinds of arguments:
class MyClass {
var score:String;
public function new(score:haxe.extern.EitherType<String, Int>) {
// technically there's no need for an if-else in this particular case, since there's
// no harm in calling `Std.string()` on something that's already a string
if (Std.is(score, String)) {
this.score = score;
} else {
this.score = Std.string(score);
}
}
}
However, I wouldn't recommend this approach, haxe.extern.EitherType is essentially Dynamic under the hood, which is bad for type safety and performance. Also, EitherType is technically only intended to be used on externs.
A more type-safe, but also slightly more verbose option would be haxe.ds.Either<String, Int>. Here you'd have to explicitly call the enum constructors: new MyClass(Left("100")) / new MyClass(Right(100)), and then use pattern matching to extract the value.
An abstract type that supports implicit conversions from String and Int might also be an option:
class Test {
static function main() {
var s1:Score = "100";
var s2:Score = 100;
}
}
abstract Score(String) from String {
#:from static function fromInt(i:Int):Score {
return Std.string(i);
}
}
Finally, there's also an experimental library that adds overloading support with macros, but I'm not sure if it supports constructors.
I recommend to use type parameter
class MyClass<T> {
var score:String;
public function new(score:T) {
this.score = Std.string(score);
}
}
You can also use type parameter at constructor
class MyClass {
var score:String;
public function new<T>(score:T) {
this.score = Std.string(score);
}
}
However, T used at constructor fails at runtime (CS and Java), it is not fixed yet (Haxe 4). Otherwise, you could do this
class MyClass {
var score:String;
#:generic public function new<#:const T>(score:T) {
this.score = Std.is(T, String) ? untyped score : Std.string(score);
}
}
which nicely produce code like this (CS)
__hx_this.score = ( (( T is string )) ? (score) : (global::Std.#string(score)) );
causing Std.string() to be called only if T is not a String.
Hej,
With a simple example as it is, you can just do something like that function new( ?s : String, ?n : Int ){} and Haxe will use the correct argument by type. But you'll be able to do new() and maybe you don't want.
I am using getter/setter accessors in TypeScript. As it is not possible to have the same name for a variable and method, I started to prefix the variable with a lower dash, as is done in many examples:
private _major: number;
get major(): number {
return this._major;
}
set major(major: number) {
this._major = major;
}
Now when I use the JSON.stringify() method to convert the object into a JSON string, it will use the variable name as the key: _major.
As I don't want the JSON file to have all keys prefixed with a lower dash, is there any possibility to make TypeScript use the name of the getter method, if available? Or are there any other ways to use the getter/setter methods but still produce a clean JSON output?
I know that there are ways to manually modify the JSON keys before they are written to the string output. I am curious if there is simpler solution though.
Here is a JSFiddle which demonstrates the current behaviour.
No, you can't have JSON.stringify using the getter/setter name instead of the property name.
But you can do something like this:
class Version {
private _major: number;
get major(): number {
return this._major;
}
set major(major: number) {
this._major = major;
}
toJsonString(): string {
let json = JSON.stringify(this);
Object.keys(this).filter(key => key[0] === "_").forEach(key => {
json = json.replace(key, key.substring(1));
});
return json;
}
}
let version = new Version();
version.major = 2;
console.log(version.toJsonString()); // {"major":2}
based on #Jan-Aagaard solution I have tested this one
public toJSON(): string {
let obj = Object.assign(this);
let keys = Object.keys(this.constructor.prototype);
obj.toJSON = undefined;
return JSON.stringify(obj, keys);
}
in order to use the toJSON method
I think iterating through the properties and string manipulating is dangerous. I would do using the prototype of the object itself, something like this:
public static toJSONString() : string {
return JSON.stringify(this, Object.keys(this.constructor.prototype)); // this is version class
}
I've written a small library ts-typed, which generate getter/setter for runtime typing purpose. I've faced the same problem when using JSON.stringify(). So i've solved it by adding a kind of serializer, and proposing to implement a kind of toString (in Java) buy calling it toJSON.
Here is an example:
import { TypedSerializer } from 'ts-typed';
export class RuntimeTypedClass {
private _major: number;
get major(): number {
return this._major;
}
set major(major: number) {
this._major = major;
}
/**
* toString equivalent, allows you to remove the _ prefix from props.
*
*/
toJSON(): RuntimeTypedClass {
return TypedSerializer.serialize(this);
}
}
A new answer to an old question. For situations where there is no private field for a getter/setter, or where the private field name is different to the getter/setter, we can use the Object.getOwnPropertyDescriptors to find the get methods from the prototype.
https://stackoverflow.com/a/60400835/2325676
We add the toJSON function here so that it works with JSON.stringify as mentioned by other posters. This means we can't call JSON.stringify() within toJSON as it will cause an infinite loop so we clone using Object.assign(...)
I also removed the _private fields as a tidyup measure. You may want to remove other fields you don't want to incude in the JSON.
public toJSON(): any {
//Shallow clone
let clone: any = Object.assign({}, this);
//Find the getter method descriptors
//Get methods are on the prototype, not the instance
const descriptors = Object.getOwnPropertyDescriptors(Object.getPrototypeOf(this))
//Check to see if each descriptior is a get method
Object.keys(descriptors).forEach(key => {
if (descriptors[key] && descriptors[key].get) {
//Copy the result of each getter method onto the clone as a field
delete clone[key];
clone[key] = this[key]; //Call the getter
}
});
//Remove any left over private fields starting with '_'
Object.keys(clone).forEach(key => {
if (key.indexOf('_') == 0) {
delete clone[key];
}
});
//toJSON requires that we return an object
return clone;
}
Isn't dynamic but it work
export class MyClass{
text: string
get html() {
return this.text.toString().split("\n").map(e => `<p>${e}</p>`).join('');
}
toJson(): string {
return JSON.stringify({ ...this, html: this.html })
}
}
In calling
console.log(myClassObject.toJson())
That is, I'm trying to invoke one constructor from another, and then construct further. I can't figure out from documentation if it can be done.
Here's a contrived example, in case it helps:
class Chipmunk {
Chipmunk.named(this.name);
Chipmunk.famous() {
this.named('Chip'); // <-- What, if anything, goes here?
this.fame = 1000;
}
}
var chip = new Chimpmunk.famous();
There are two possible ways to do this:
class Chipmunk {
String name;
int fame;
Chipmunk.named(this.name, [this.fame]);
Chipmunk.famous1() : this.named('Chip', 1000);
factory Chipmunk.famous2() {
var result = new Chipmunk.named('Chip');
result.fame = 1000;
return result;
}
}
Chipmunk.famous1() is a redirective constructor. You can't assign properties in this one, so the constructor you are calling has to allow all the properties you want to set. That's why I added fame as an optional parameter. In this case you could make name and fame final.
Chipmunk.famous2() is a factory constructor and can just create the instance you want. In this case, fame couldn't be final (obviously it could be if you used the fame parameter in the named constructor).
The first variant would probably be the preferable one for your use case.
This is the documentation in the language spec:
A generative constructor consists of a constructor name, a constructor parameter list, and either a redirect clause or an initializer list and an optional body.
https://dart.dev/docs/spec/latest/dart-language-specification.html#h.flm5xvbwhs6u
The init pattern could be used here : the constructor just calls an init function defined in the class.
It can have an advantage over the redirective constructor in some cases, i can think of two right now :
- if you are saving/restoring the state of your object (in this case, you just write once the restore part).
- if you are pooling (recycling) your objects and need to 'refresh' them when you revive them.
class Chipmunk {
Chipmunk.named(string newName) { nameInit(newName) };
Chipmunk.famous() {
famousInit();
}
nameInit(string newName) {
name = newName ;
}
famousInit() {
nameInit('Chip');
fame = 1000;
}
string name;
num fame;
}
var chip = new Chimpmunk.famous();
Another way is to use a static method that returns an instance of the same class.
It's technically not a constructor and it's not recommended by the Dart Linter, but it can help in situations where you need to choose which constructor to call depending on the value of an argument.
class Chipmunk {
Chipmunk.named(this.name, [this.fame]);
// ignore: prefer_constructors_over_static_methods
static Chipmunk withFame(int fame) {
if (fame > 100) {
return Chipmunk.named('Super Famous Chip', fame);
}
return Chipmunk.named('Chip', fame);
}
String name;
int? fame;
}
var superFamousChip = Chipmunk.withFame(110);
If I am using an object literal as the default value for a property in ActionScript, will this create a single object which is referenced by all instances, or a new object for each instances?
For example, if I created multiple instances of this AddressBook...
class AddressBook {
private var numbersByName:Object = {};
public function addNumber(name:String, number:String):void {
numbersByName[name] = number;
}
public function getNumber(name:String):String {
return numbersByName[name];
}
...would they behave properly, or would they use the same numbersByName object, causing all changes in one also apply to the others?
Even if each AddressBook instance does get its own object, it's conceivable that Flash might only apply this behaviour shallowly. Consider a default of nested object literals:
class AddressBook {
private var indicies:Object = {
numberToName: {},
nameToNumber: {}
};
public function addNumber(name:String, number:String):void {
indicies.numberToName[number] = name;
indicies.nameToNumber[name] = number;
}
public function getName(number:String):String {
return indicies.numberToName[number];
}
public function getNumber(number:String):String {
return indicies.nameToNumber[number];
}
}
Is the entire nested literal created anew for each instance, or do the different indicies objects share the same numberToName objects?
Generally, will there be a between the properties of a class whose property has an object literal default...
class MyClass {
private var myProperty:Object = { a: [1, 2, 3], b: { d: 4, e: 5 } };
}
...and one which initializes it in the constructor?
class MyClass {
private var myProperty:Object;
public function MyClass() {
myProperty = { a: [1, 2, 3], b: { d: 4, e: 5 } };
}
}
When you create an instance of your class, it will have its own unique variables.
So
var class_a:AddressBook = new AddressBook();
var class_b:AddressBook = new AddressBook();
class_a.var = 1;
class_b.var = 2;
trace(class_a.var) //traces 1.
Completely new objects will be created, none will be re-used. You get the same result whether you use the simple AddressBook or the one with nested object literals:
trace("None instantiated yet.")
var a:* = new AddressBook;
trace("A instantiated.");
var b:* = new AddressBook;
trace("B instantiated.");
a.addNumber("John", "555-5555");
trace(a.getNumber("John")); // prints 555-5555
trace(b.getNumber("John")); // prints null
Set a breakpoint inside of the object literal. What is the output when it breaks the first time?
None instantiated yet.
The second time?
None instantiated yet.
A instantiated.
The object literal is evaluated each time an instance is created, and at no other time.
Unfortunately, I was unable to find a definitive reference for this behaviour. The ActionScript "specification" doesn't contain enough detail. Better references would be appreciated.
Consider the following example dataclass:
[RemoteClass]
public class SOTestData {
public var i:int;
public function SOTestData(i:int) {
this.i = i;
}
}
As I understand, the RemoteClass metadata-tag should ensure that when an object of this class gets sreialized, the type information is preserved.
I used the following program to test:
public class SOTest extends Sprite {
public function SOTest() {
var data:SharedObject = SharedObject.getLocal("SOTest");
if (data.data.object) {
try {
var stored:SOTestData = data.data.object;
trace(stored.i);
} finally {
data.clear();
}
}
else {
data.data.object = new SOTestData(15);
data.flush();
}
}
}
Here the first run writes the data, seconds reads and clears. Running this, I still get a class cast error. Indeed, in the SharedObject there is no type information stored.
I don't think i'm using the metadata wrong, could it maybe be that the compiler doesn't know what to do with it? I don't get any compiler errors/warnings, although when i use some inexistant tag it doesn't complain either. I'm using Flex 4.6 SDK with FlashDevelop as IDE.
EDIT:
Below is the shared object. As you can see, the type is saved as "Object" instead of the actual type.
so = [object #2, class 'SharedObject'] {
data: [object #0, class 'Object'] {
object: [object #1, class 'Object', dynamic 'False', externalizable 'False'] {
i: 15,
},
}
}
I've only used RemoteClass for making AMF RemoteObject calls; I didn't think it had anything to do w/ Shared Objects. Per the docs
Use the [RemoteClass] metadata tag to register the class with Flex so
that Flex preserves type information when a class instance is
serialized by using Action Message Format (AMF). You insert the
[RemoteClass] metadata tag before an ActionScript class definition.
The [RemoteClass] metadata tag has the following syntax:
As best I can tell from the code you provided, you are not serializing the object in AMF format.
I believe your class cast error is due to the fact that you aren't casting your class. Shared Objects always come back as generic Objects. Try this:
var stored:SOTestData = data.data.object as SOTestData ;
Here is some code from an application I use. First the value object which will get serialized in a shared object:
package com.login.vos
{
[RemoteClass(alias="com.login.vos.UserVO")]
public class UserVO
{
public function UserVO()
{
}
public var firstName :String;
public var lastName :String;
public var userID :Number;
}
}
The the code to save the object:
public static function saveUserVO(userVO:UserVO):void{
var userSharedObject :SharedObject = SharedObject.getLocal('userVO') ;
userSharedObject.data.userVO = userVO;
userSharedObject.flush();
}
And finally, the code to load the objecT:
public static function getUserVO():UserVO{
var userSharedObject :SharedObject = SharedObject.getLocal('userVO')
if(userSharedObject.size <=0){
return null;
}
return userSharedObject.data.userVO as UserVO;
}
The only obvious difference between this and the code by the original poster is that I'm specifying an alias in the RemoteClass metadata.