封装原生对象属性
青语言的对象Obj类中定义了一个object类型的属性Raw,实现封装功能的对象可以保存在这里。
例如在SoundDriver.cs文件中,我们需要通过WaveOutEvent对象来实现音频播放,那么就可以把创建出来的对象赋值给Raw。
如果还有其他需要保存的数据,也可以自己额外定义属性,例如:
public string currFile = "";
这里我们定义了一个属性记录当前播放的文件。
对原生对象属性的操作需要进行相应的转换才能匹配青语言中的数据类型,我们通过实现Prop类来完成这个操作。使用方式是定义一个继承Prop的类,并实现其中的Qget和Qset方法。例如:
class BindCurrFile : Prop {
public BindCurrFile(Obj obj) : base(obj) { }
public override Expr Qget(Ctx? ctx=null) {
return new Expr(TP.Str, obj.currFile);
}
public override Expr Qset(Expr val, Ctx? ctx = null) {
return Expr.Err("禁止直接修改播放器文件");
}
}
这个例子里,我们是对刚才定义的currFile 进行操作,Qget方法用来进行取值,得到当前播放的文件名。由于我们不支持直接修改播放的音乐文件,所以Qset直接返回一个错误。
Prop里定义了一个obj属性,用于存储对应的对象,构造方法统一写作public BindCurrFile(Obj obj) : base(obj) { }
那么,之后我们都可以通过obj对定义的对象进行操作。
我们再来看修改音量的例子:
class BindVolume : Prop {
public BindVolume(Obj obj) : base(obj) { }
public override Expr Qget(Ctx? ctx = null) {
if(obj.Raw is not WaveOutEvent) {
return Expr.NoneExpr;
}
return new Expr(TP.Float, (decimal)(obj.Raw.Volume));
}
public override Expr Qset(Expr val, Ctx? ctx = null) {
if(val.Tp == TP.Int) {
obj.Raw.Volume = (float)val.Int();
}else if(val.Tp == TP.Float) {
obj.Raw.Volume = (float)val.Float();
} else {
return Expr.Err("音乐播放器音量必须为数字类型");
}
return val;
}
}
之前我们说过,Raw实际存放的是WaveOutEvent对象,那么当我们需要获取音量时,就通过Raw来获取,但WaveOutEvent中的音量信息是float类型,青语言中的小数是decimal,所以我们返回TP.Float类型的Expr时,需要先转换为decimal。
同样,在设置音量时,我们通过实现Qset方法来实现,我们运行传入青语言的整数或小数,那么设置时还需转换为float类型。
以上,我们只是实现了对属性封装类的定义,使用前还需对将其绑定到对象中:
Map["#文件"] = new Expr(TP.Prop, new BindCurrFile(this));
Map["#音量"] = new Expr(TP.Prop, new BindVolume(this));
同样,属性以#开头,绑定的值是TP.Prop类型的Expr,然后把我们定义的绑定属性的Prop类作为Expr内的值,并把这个对象传入即可。
青语言对对象取值时,如果遇到对象内的属性是TP.Prop类型的值,那么会通过Qget方法获取转换后的值,相应的进行赋值操作时,或通过Qset 方法进行设值。这样,我们就完成了对原生对象属性的封装。