Hierarchy modifiers control the way elements of a class behave within their class hierarchy.
By default, all methods in .Net are non-VIRTUAL, meaning they cannot be overridden by methods with the same name (and signature - parameter and return types) in descendant classes. In the following example, calling a method from code inside the Parent class, results in calling the version of the method defined in this particular class in the class hierarchy, not the method with the same name and signature defined in the Child class (even though the object we are testing is an instance of the Child class):
FUNCTION Start() AS VOID
LOCAL oTest AS Child
oTest := Child{}
oTest:DoTest()
CLASS Parent
METHOD SomeMethod() AS VOID
? "Parent method was called"
METHOD DoTest() AS VOID
SELF:SomeMethod() // calls the method of this Parent class, not the same named one defined in the Child class
RETURN
END CLASS
CLASS Child INHERIT Parent
METHOD SomeMethod() AS VOID
? "Child method was called"
END CLASS
This behavior is different from that of Visual Objects, FoxPro and Xbase++, where ALL methods are always considered VIRTUAL. In order to make a method overridable from descendant classes, it needs to be defined as VIRTUAL. In the following code, calling a VIRTUAL method from code in the Parent class, results in calling the version of the method defined in the Child class, since it has overridden the parent method:
CLASS Parent
METHOD NonVirtualMethod() AS VOID // cannot be overridden in child class
? "Parent non virtual method was called"
VIRTUAL METHOD VirtualMethod() AS VOID // can be overridden
? "Parent virtual method was called"
METHOD DoTest() AS VOID
SELF:NonVirtualMethod() // calls method in parent
SELF:VirtualMethod() // calls method in child
RETURN
END CLASS
CLASS Child INHERIT Parent
METHOD NonVirtualMethod() AS VOID // this is completely different method to the parent one, even though it has the same name
? "Child non virtual method was called"
OVERRIDE METHOD VirtualMethod() AS VOID // overrides the same named method of the parent class
? "Child virtual method was called"
END CLASS
Note that VirtualMethod() in the child class is declared with the OVERRIDE modifier; this tells the compiler that we have intentionally overridden a parent method with the same name. The OVERRIDE modifier is not mandatory in X# and can be omitted, but using it makes the code more self-explanatory and allows the compiler to make additional compile time checks. If OVERRIDE is used on a method to override a parent method that is not VIRTUAL, or it does not even exist (or is spelled with a different name or has a different signature in the parent class), then a compiler error will be reported. For this reason, it is recommended to explicitly declare methods overriding parent methods with the OVERRIDE keyword. It is also possible to enforce the use of the OVERRIDE keyword in the compiler, by enabling the compiler option "Enforce OVERRIDE" (/enforceoverride).
In the above code, the compiler does report a warning on the NonVirtualMethod() defined in the child class: "warning XS0108: 'Child.NonVirtualMethod()' hides inherited member 'Parent.NonVirtualMethod()', use the new keyword if hiding was intended". This issues a warning about the definition of a method in the child class that has the same name as a non-virtual method in the parent class, as this may have been done by accident (either accidentally using the same name, or forgetting to define the base method as virtual). In order to tell the compiler that the use of the same name in a child method was intentional (and prevent the warning), the NEW modifier keyword can be used, which explicitly marks the method as one that does override the base one:
CLASS Child INHERIT Parent
NEW METHOD NonVirtualMethod() AS VOID // explicitly mark the method as new one, different to the parent method
? "Child non virtual method was called"
OVERRIDE METHOD VirtualMethod() AS VOID // overrides the same named method of the parent class
? "Child virtual method was called"
END CLASS
For already existing code ported from older systems like Visual Objects where methods are always VIRTUAL, it can be tedious to manually add the VIRTUAL modifier keyword to all the methods that need it. For this reason, you can instruct the compiler to automatically treat ALL methods as virtual, by using the "All instance methods virtual" (/vo3) compiler option. However, it is highly recommended to instead review the code and manually add the VIRTUAL modifier only where it is truly needed.
Note that a method in a child class can override a parent class method only if it has the exact same signature with it, meaning it has the exact same name (even the exact same casing of the name, if the Case sensitive type names (/cs) compiler option is enabled), parameter count and types and return type with the base method. If the two methods are different on any of those aspects, then they are considered as completely different methods, and one cannot override the other:
CLASS Parent
VIRTUAL METHOD VirtualMethod(n AS INT) AS VOID // child class has same named method but with different signature
END CLASS
CLASS Child INHERIT Parent
OVERRIDE METHOD VirtualMethod(c AS STRING) AS INT // compiler error XS0115: 'VirtualMethod': no suitable method found to override
RETURN 0
END CLASS
The VIRTUAL/OVERRIDE/NEW modifiers do not apply to methods only, but also to properties or ACCESS/ASSIGN pairs (and also to events, but it makes little sense in declaring/overriding virtual events):
FUNCTION Start() AS VOID
Child{}:DoTest()
CLASS Parent
VIRTUAL PROPERTY TestProp AS STRING GET "parent"
ACCESS TestAccess AS STRING
RETURN "parent"
METHOD DoTest() AS VOID
? SELF:TestProp // child, because the property is overridden in the child class
? SELF:TestAccess // parent, because the access is not virtual
RETURN
END CLASS
CLASS Child INHERIT Parent
OVERRIDE PROPERTY TestProp AS STRING GET "child"
NEW ACCESS TestAccess AS STRING
RETURN "child"
END CLASS
Finally, also class fields can be declared as NEW (but not as VIRTUAL), in order to differentiate them from fields with the same name in a parent class:
FUNCTION Start() AS VOID
Child{}
CLASS Parent
EXPORT cField := "parent" AS STRING
CONSTRUCTOR()
? SELF:cField // parent, refers to the filed defined in the parent class
END CLASS
CLASS Child INHERIT Parent
NEW EXPORT cField := "child" AS STRING
CONSTRUCTOR()
SUPER()
? SELF:cField // child, refers to the filed defined in the parent class
END CLASS
Please note that the XBase++ dialect also has the modifiers:
DEFERRED |
Synonym for ABSTRACT |
INTRODUCE |
Synonym for NEW |
SYNC |
Code inside the method is protected from running simultaneously in different threads |
Using the SEALED modifier on a class, prevents it from being inherited by a child class.
This can be useful in order to prevent users of a class/library to alter its functionality in a subclass and possibly introduce problems in it.
It also allows the compiler to emit more efficient code, because it knows that there will be no subclasses of a type.
SEALED CLASS Parent
END CLASS
CLASS Child INHERIT Parent // compiler error XS0509: cannot derive from sealed type 'Parent'
END CLASS
SEALED can also be used on specific (virtual) methods/properties to prevent overriding only them in a subclass:
FUNCTION Start() AS VOID
Child{}:DoTest()
CLASS Base
VIRTUAL METHOD VirtualMethod() AS VOID
? "base"
METHOD DoTest() AS VOID
SELF:VirtualMethod() // parent, because method is overridden in the parent class, but cannot be overridden in the subclass of parent
END CLASS
CLASS Parent INHERIT Base
SEALED OVERRIDE METHOD VirtualMethod() AS VOID
? "parent"
END CLASS
CLASS Child INHERIT Parent // compiler error
// OVERRIDE METHOD VirtualMethod() AS VOID // compiler error XS0239: cannot override method because it is sealed
NEW METHOD VirtualMethod() AS VOID // can only define a NEW method with the same name and signature, which does not override the parent method
? "child"
END CLASS
The ABSTRACT modifier can be used on a class in order to prevent it from being accidentally directly instantiated. Only classes inheriting (and implementing additional functionality) from an abstract class can be instantiated. This is useful for classes that implement base functionality, but for which it does not make sense to create an instance of them directly (like the Control class in System.Windows.Forms or the same named class in the VOSDK):
FUNCTION Start() AS VOID
LOCAL o AS OBJECT
o := Child{} // OK
o := Parent{} // Compiler error XS0144: cannot create an instance of the abstract type
ABSTRACT CLASS Parent
END CLASS
CLASS Child INHERIT Parent
END CLASS
Abstract classes can also define abstract methods or properties, which dictates that it is mandatory to implement them in classes inheriting from the abstract class. Abstract methods are implicitly virtual so they can be overridden, and it is a compiler error not providing an implementation in a child class:
ABSTRACT CLASS Parent
METHOD BasicFunctionality() AS VOID // does not need to be overriden in subclass
ABSTRACT METHOD MustImplementInChild() AS VOID // implementation must be defined in subclasses
END CLASS
CLASS Child INHERIT Parent
// Omitting this would result to a compiler error that Child class does not implement inherited abstract method
OVERRIDE METHOD MustImplementInChild() AS VOID
END CLASS
Please note that XBase++ dialect used the modifier FINAL, which is equivalent to SEALED. The FREEZE modifier is not supported by X#.