Late Bind Calls
SAL distinguishes an ordinary function call from a call written with the double-dot notation, such as ..Calculate(). The latter requests late-bound selection of the implementation. That distinction matters when code inherited from a base class runs on an instance of a derived class.
Ice Porter preserves the distinction with a generated virtual wrapper. With the default prefix, Calculate() is the ordinary implementation and __Calculate() is the virtual dispatch entry point. The underscores are a naming convention chosen by the generator; they have no special meaning to the C# compiler. No C# method name starts with a dot.
One Object, Two Call Paths
Suppose a base rule calculates 10, and a derived rule supplies 20. A method declared on the base class can deliberately call its own implementation, or ask for the late-bound implementation on the actual object.
- Inherited methodCode declared in BaseRuleActive
- Base implementationCalculate() returns 10
- Virtual wrapper__Calculate() dispatches
- Derived implementationCalculate() returns 20
The object is a DerivedRule, but this inherited method body was declared in BaseRule.
Actual object: DerivedRuleA conceptual sequence, not a live runtime trace. Playback advances every four seconds. The full explanation follows below.
Base-declared ordinary call on a DerivedRule object:
EvaluateDirect() → BaseRule.Calculate() → 10
Base-declared late-bound call on the same object:
EvaluateLate() → virtual __Calculate()
→ DerivedRule.__Calculate()
→ DerivedRule.Calculate() → 20
The important change is which method is selected, not whether another object is created. Both calls above use the same DerivedRule instance.
SAL Intent and C# Translation
These SAL function bodies differ only in the call expression. Both return the calculation result; neither merely calls a function and discards its result.
Function: EvaluateDirect
Returns
Number:
Actions
Return Calculate()
Function: EvaluateLate
Returns
Number:
Actions
Return ..Calculate()
The following complete C# classes isolate the generated dispatch pattern. They use int to make the example runnable without a PPJ installation. Converted SAL numeric methods commonly use SalNumber; that does not change the dispatch principle. Exact generated names and additional context code depend on the conversion options.
public class BaseRule
{
public int Calculate() { return 10; }
public int EvaluateDirect() { return Calculate(); }
public int EvaluateLate() { return __Calculate(); }
public virtual int __Calculate() { return Calculate(); }
}
public class DerivedRule : BaseRule
{
public new int Calculate() { return 20; }
public override int __Calculate() { return Calculate(); }
}
new explicitly hides the ordinary base method. It does not override it. The override on the wrapper is what makes the late-bound path reach the derived implementation. See Microsoft's explanation of new and override.
Expected Results
var baseObject = new BaseRule();
var derivedObject = new DerivedRule();
BaseRule baseReference = derivedObject;
Console.WriteLine(baseObject.EvaluateDirect()); // 10
Console.WriteLine(baseObject.EvaluateLate()); // 10
Console.WriteLine(derivedObject.EvaluateDirect()); // 10
Console.WriteLine(derivedObject.EvaluateLate()); // 20
Console.WriteLine(baseReference.EvaluateLate()); // 20
EvaluateDirect still returns 10 on the derived object because its body was declared in BaseRule and calls the nonvirtual BaseRule.Calculate. Giving the object a base-typed reference does not disable virtual dispatch: baseReference.EvaluateLate() still returns 20.
A direct call written as derivedObject.Calculate() returns 20 because that expression selects the derived class's hidden method. That is a different call site from the inherited EvaluateDirect body. Keeping the call site visible prevents the misleading rule that “ordinary calls always use the base class.”
Download LateBindDemo.cs for a standalone console version with seven result checks. Compile it in a C# console project or with your installed C# compiler. It demonstrates dispatch, not a complete Ice Porter conversion.
Secondary Bases
C# permits one direct class base. In generated multiple-inheritance code, a secondary base is represented by an owned object. Its virtual methods cannot be overridden directly by a class outside its C# inheritance chain.
For this path, Ice Porter adds a late-bind interface and a reference to the final derived object. The secondary wrapper can then route through that interface instead of stopping at its own implementation. See Multiple Inheritance for the interface and _derived code, and the dispatch walkthrough for the object relationships.
Preserve Dispatch Intent
Before refactoring a wrapper, test a base instance, a derived instance, and a base-typed reference to that derived instance. Include calls originating inside the base class, not only calls made directly by the test harness.
Do not replace __Calculate() with Calculate() merely because the wrapper's default body calls it. Doing so bypasses derived overrides. Keep wrapper signatures and configured prefixes consistent across overrides and secondary-base interfaces. Also preserve qualified base calls whose purpose is to select a particular implementation.