InstanceType

The InstanceType type can be used as return type for class members to indicate that the member returns a value of the same (or more concrete) type as the instance it is being called on.

This allows for class hierarchies where more concrete descendants can override a method, and (be know to) return more concrete values that match their class. Consider the following example:

type
  Employee = public class
  public
    property Name;
    method Copy: Employee; virtual; 
  end;

  Manager = public class(Employee)
  public
    property Underlings: List<Person>;
    method Copy: Employee; override;
  end;
public class Employee
{
    public string Name { get; set; }
    public virtual Employee Copy() { ... }
}

public class Manager : Employee
{
    public List<Person> Underlings { get; set; }
    public override Employee Copy() { ... }
}
public class Employee {
    public var Name
    public func Copy() -> Employee { ... }
}

public class Manager : Employee
{
    public var Underlings: [Person]
    public override func Copy() -> Employee { ... }
}
public class Employee
{
    public string Name { __get; __set; }
    public virtual Employee Copy() { ... }
}

public class Manager : Employee
{
    public List<Person> Underlings { __get; __set; }
    public override Employee Copy() { ... }
}

In this example of a class hierarchy, the Copy() method can be used to create a copy of a given employee record. But, due to the nature of how traditional polymorphism works, calling Copy on an instance of the more concrete Manager class still returns the base class – requiring unnecessary casting:

var m := lMyManager.Copy();
var u := m.Underlings.Count; // fails
var m = lMyManager.Copy();
var u = m.Underlings.Count; // fails
let m = lMyManager.Copy()
let u = m.Underlings.Count // fails
var m = lMyManager.Copy();
var u = m.Underlings.Count; // fails

Here, we can't access the Underlings property of the cloned Manager, because even though we know that m refers to a Manager, the compiler only knows that m is an Employee.

Simply changing the return type of Copy to InstanceType fixes this:

type
  Employee = public class
  public
    property Name;
    method Copy: InstanceType; virtual; 
  end;

  Manager = public class(Employee)
  public
    property Underlings: List<Person>;
    method Copy: InstanceType; override;
  end;
public class Employee
{
    public string Name { get; set; }
    public virtual InstanceType Copy() { ... }
}

public class Manager : Employee
{
    public List<Person> Underlings { get; set; }
    public override InstanceType Copy() { ... }
}
public class Employee {
    public var Name
    public func Copy() -> Self { ... }
}

public class Manager : Employee
{
    public var Underlings: [Person]
    public override func Copy() -> Self { ... }
}
public class Employee
{
    public string Name { __get; __set; }
    public virtual InstanceType Copy() { ... }
}

public class Manager : Employee
{
    public List<Person> Underlings { __get; __set; }
    public override InstanceType Copy() { ... }
}

Because the compiler knows that lMyManager is a Manager, it know can infer that the result of Copy() will also be a Manager instance. So the following code now works, without cast:

var m := lMyManager.Copy();
var u := m.Underlings.Count;
var m = lMyManager.Copy();
var u = m.Underlings.Count;
let m = lMyManager.Copy()
let u = m.Underlings.Count
var m = lMyManager.Copy();
var u = m.Underlings.Count;

But of course Copy() still behaves fully polymorphically, like any virtual/override method. When calling Copy() on an instance of a Manager that's stored in a variable typed Employee, the same override Copy() method will be called at runtime – but of course in that case, the compiler will merely assume the result is an Employee:

var e := lSomeEmployee.Copy(); // could be a Manager, but we don't know
var u := e.Underlings.Count; // fails! we can't assume e is a Manager, here
var e = lSomeEmployee.Copy(); // could be a Manager, but we don't know
var u = e.Underlings.Count; // fails! we can't assume e is a Manager, here
var e = lSomeEmployee.Copy() // could be a Manager, but we don't know
var u = e.Underlings.Count // fails! we can't assume e is a Manager, here
var e := lSomeEmployee.Copy(); // could be a Manager, but we don't know
var u := e.Underlings.Count; // fails! we can't assume e is a Manager, here

InstanceType in Operators

InstanceType can also be used when defining Custom Operators. Operators must use the declaring type for at least one parameter and/or result type, and rather than specifying the actual type name, Instancetype can be used as a a placeholder. This makes operators more robust to type renames, and makes it easier to copy/paste and reuse code when declaring similar operators on different types.

type
  MyClass = public class
  
    operator Implicit(aValue: String): InstanceType;
    begin
      result := ... //convert from String to MyClass
    end;
  
  end;
public class MyClass
{  
    public static implicit operator InstanceType(string value)
    {
        return ... //convert from String to MyClass
    }
}
public class MyClass {  
    func __implicit(_ aValue: String) -> InstanceType {
      return ... //convert from String to MyClass
    }
}

See Also

Version Notes

As of Elements 10, InstanceType is now supported on all platforms, not just Cocoa (where it has always been available).