Page 1 of 1
Inheritance
Posted: Tue Feb 15, 2022 3:24 am
by ThomasWYale
In my system, there are the following classes:
CLASS Frame
CLASS FE
CLASS FrameEx INHERIT Frame
CLASS FEEx INHERIT FE (which XSharp VOXPorter converted to PARTIAL CLASS FEEx INHERIT FE)
Each of these four classes has it's own init method (or rather CONSTRUCTOR method). I anticipated that whatever class is instantiated, it would run that CONSTRUCTOR code. But when I step through through the debugger where an instantiation of FrameEx occurs, and look at the call stack, it appears to be executing the CONSTRUCTOR for FrameEx twice, despite that at the top of the call stack, it's actually executing the CONSTRUCTOR for the inherited class Frame instead (please see screenshot below).
- inheritance_problem.jpg (124.11 KiB) Viewed 834 times
).
Why would the CONSTRUCTOR Frame execute instead of the one for FrameEx? Is it possible that while X# is displaying "FrameEx", X# is ignoring the "Ex" in "FrameEx" and just executing the one with the inherited class with the shortest name? Should the inheriting classes be given a more distinctive name to prevent this from happening?
Inheritance
Posted: Tue Feb 15, 2022 7:20 am
by robert
Thomas,
Can you share the code?
Your expectations are correct.
I can't read the screen shots. Are the "Ex" constructors calling the SUPER() constructors ?
Robert
Inheritance
Posted: Tue Feb 15, 2022 7:37 am
by Chris
Hi Thomas,
It's the correct constructor that it's being called every time, as you can see the line specified in the 3rd column is different in the two lines.
The "class" specified in the first column is the actual class name of the active SELF object, which is indeed the name of the last class in the inheritance, not the name of the class where the debugger is pointing at right now in the editor. It is unfortunately a bit complicated to make it show the class name in the inheritance tree, but will give it a try.
Is there some other problem that you were trying to debug and found this?
.
Inheritance
Posted: Thu Feb 17, 2022 3:00 pm
by ThomasWYale
Yes, here it is. Another class instantiates a FrameEx object:
Code: Select all
AAdd(oExSyntaxRef:aFrameEx,(oFrameEx:=FrameEx{nVerbLoc,oVerb,TRUE}))
These are the class definitions of Frame and FrameEx:
Code: Select all
CLASS FrameEx INHERIT Frame
EXPORT lPositive AS LOGIC
EXPORT aFEsLiteral AS ARRAY
EXPORT aFEsObj AS ARRAY
EXPORT aRelations AS ARRAY
EXPORT aRelatedFrames AS ARRAY
EXPORT nVerbRef AS BYTE
EXPORT nScore AS BYTE
EXPORT aEmbeddingFEs AS ARRAY
EXPORT lFEsConform AS LOGIC
EXPORT aUnassignedObj AS ARRAY
EXPORT lViaVN AS LOGIC
CLASS Frame
EXPORT nRec AS LONG
EXPORT ccName AS STRING
EXPORT cDef AS STRING
EXPORT aFEs AS ARRAY
EXPORT aGramObjs AS ARRAY
EXPORT cRelation AS STRING
EXPORT aEmbedded AS ARRAY
And the CONSTRUCTORs:
Code: Select all
CONSTRUCTOR(lInitialize) // for Frame
LOCAL y AS BYTE
LOCAL cRec AS STRING
LOCAL aSFE[0],aOFE[0],aOtherFE[0] AS ARRAY
LOCAL oFE AS FE
SELF:aGramObjs:={}
SELF:cRelation:=""
IF lInitialize
SELF:nRec:=0
SELF:ccName:=""
SELF:cDef:=""
SELF:aFEs:={}
ELSE
SELF:nRec:=RecNo()
SELF:ccName:=Trim(cName)
SELF:cDef:=Def
SELF:aFEs:={}
cRec:=base16(RecNo(),2)
DbSelectArea("FE")
DbSeek(cRec)
AAdd(SELF:aFEs,FE{TRUE})
WHILE cRec==nFrame
oFE:=FE{FALSE}
IF oFE:nGramSlot=0
AAdd(aSFE,oFE)
ELSEIF oFE:nGramSlot=1
AAdd(aOFE,oFE)
ELSE
AAdd(aOtherFE,oFE)
ENDIF
DbSkip()
ENDDO
FOR y:=1 UPTO ALen(aSFE)
AAdd(SELF:aFEs,aSFE[y])
NEXT
FOR y:=1 UPTO ALen(aOFE)
AAdd(SELF:aFEs,aOFE[y])
NEXT
FOR y:=1 UPTO ALen(aOtherFE)
AAdd(SELF:aFEs,aOtherFE[y])
NEXT
SELF:aEmbedded:=ArrayNew(ALen(SELF:aFEs))
AFill(SELF:aEmbedded,FALSE)
ENDIF
RETURN SELF
CONSTRUCTOR(nVerbRef,oWord,lViaVN) // for FrameEx
LOCAL nFE1Loc,nFE2Loc AS BYTE
LOCAL nFE1Rec,nFE2Rec AS LONG
LOCAL cRec AS STRING
LOCAL cElements AS STRING
SELF:nRec:=RecNo()
SELF:ccName:=Trim(cName)
IF SELF:ccName=="Change_position_on_a_scale"
nFE1Loc:=1
ENDIF
SELF:cDef:=Def
SELF:aFEs:={}
cRec:=base16(RecNo(),2)
cElements:="FrameEx:init: "+NTrim(RecNo())+" = "+cRec+CRLF
cElements+=SELF:ccName+CRLF
DbSelectArea("FE")
DbSeek(cRec)
cElements+="In FE.dbf:"+CRLF+NTrim(RecNo())+CRLF
WHILE cRec==nFrame
cElements+=NTrim(RecNo())+CRLF
DbSkip()
ENDDO
DbSelectArea("FE")
DbSeek(cRec)
AAdd(SELF:aFEs,FEEx{TRUE})
WHILE cRec==nFrame
AAdd(SELF:aFEs,FEEx{FALSE})
DbSkip()
ENDDO
SELF:aEmbedded:=ArrayNew(ALen(SELF:aFEs))
AFill(SELF:aEmbedded,FALSE)
DbSelectArea("FEFESAFR")
DbSeek(cRec)
WHILE Frme==cRec
IF RelateType="1"
nFE1Rec:=base10(FE1)
nFE1Loc:=AScan(SELF:aFEs,{|x|x:nCNameRec=nFE1Rec})
nFE2Rec:=base10(FE2)
nFE2Loc:=AScan(SELF:aFEs,{|x|x:nCNameRec=nFE2Rec})
IF SELF:aFEs[nFE2Loc]:aSubFEs==NULL_ARRAY
SELF:aFEs[nFE2Loc]:aSubFEs:={}
ENDIF
AAdd(SELF:aFEs[nFE2Loc]:aSubFEs,SELF:aFEs[nFE1Loc])
SELF:aEmbedded[nFE1Loc]:=TRUE
ENDIF
DbSkip()
ENDDO
SELF:aGramObjs:={}
SELF:cRelation:=""
SELF:lPositive:=TRUE
IF!IsNil(oWord)
SELF:aFEsLiteral:={oWord:cLiteral}
SELF:aFEsObj:={oWord}
ENDIF
SELF:aRelations:={}
SELF:aRelatedFrames:={}
SELF:nVerbRef:=nVerbRef
SELF:nScore:=0
SELF:aEmbeddingFEs:={}
SELF:lFEsConform:=TRUE
SELF:aUnassignedObj:={}
SELF:lViaVN:=lViaVN
RETURN SELF
In VO, only one of these inits is executed, depending on the CLASS. In X#, according to the callstack, both CONSTRUCTORs are executed for some reason. In that case, to answer your question, yes, instantiating FrameEx calls the SUPER() CONSTRUCTOR, which would be Frame:init(), which in X# would be Frame.ctor().
I circumvented the problem by recoding Frame and FrameEx as two separate CLASSes, as well as encapsulating CLASSes FE and FEex, with no inheritance, and it works fine. I was a little concerned that future developers, however, may require inherited or inheriting CLASSes and face the same problem.
Inheritance
Posted: Thu Feb 17, 2022 3:06 pm
by ThomasWYale
Yes, the inheritance problem here was separate from another problem that I posted on another forum topic, which dealt with using "RESULT" as a dbf field name. "RESULT" conflicted with an ACCESS attribute for the window that was using it. You suggested inserting "FIELD->(RESULT)" to refer to the field name, and I replaced all occurrences of that in the code specifically for the window. Your solution was excellent, thank you! After that, it worked fine.
Inheritance
Posted: Thu Feb 17, 2022 3:43 pm
by robert
Thomas,
In .Net if you have classes and subclasses, then the subclass is expected to call the constructor of the parent class.
Not calling the parent constructor is a "deadly sin".
What you can do is to create 2 (or more) constructors in the parent class. The default parameterless constructor can do "nothing" and can be called from the child class.
If you choose to do it that way then you will have to strongly type the parameters of the parameters of the constructor. You cannot use overloading when one of the constructors has untyped parameters (clipper calling convention).
Robert
Inheritance
Posted: Thu Feb 17, 2022 4:14 pm
by Chris
Hi Thomas,
There's a trick you can use to avoid this, add the following code anywhere in your FrameEx constructor:
this will trick the compiler into thinking you are handling calling the parent constructor yourself, and will not add implicitly code to do it, so the parent will never get called.
I am sure Robert will call me naughty for this
.
Inheritance
Posted: Thu Feb 17, 2022 4:28 pm
by robert
Inheritance
Posted: Thu Feb 17, 2022 5:05 pm
by Chris
Inheritance
Posted: Thu Feb 17, 2022 5:06 pm
by ThomasWYale
LOL