How CLR Handles Static, Non-Virtual Instance, and Virtual Instance method calls

How does method execution differs when CLR handles static, instance and virtual instance methods ? To answer them, one needs to begin by understanding a bit more on how CLR handles Managed Heap during execution of a method.

Memory Allocation in Managed Heap

When the application execution begins, as the CLR gets loaded, the thread stack and managed heap gets allocated. Prior to method execution, JIT (Just In Time) Compiler converts the IL code to native instructions. At this moment, it also detects all the types used in the method and also loads all the required/dependend assemblies needed to support the execution.

The CLR also gathers information about the data types involved from the metadata and create the required Data structures. Each time a new Type is encountered, a data structure representing Type Object is created. The Type Object contains two special fields called Type Object Pointer and Sync Block Index. The Type Object also contains static fields and a method reference table. The Method table contains an entry for each method exposed by the type.

For each instance of a type, an instance object data structure is create in the Managed Heap. The instance object would memory space for storing the instance fields in the type. Similiar to the Type Object , the instance object would also contain the two special fields – Type Object Pointer and Sync Block Index.

The Type Object Pointer of the instance object points to the corresponding type object. At this point, one might wonder what would the Type Object Pointer of the type object point to ? Well, it points to a special type object of type System.Type. The next question that might arise in your mind would be, what would type object pointer of the System.Type type object refers to ? The answer is – it refers to self. Interesting.

Once the local variables are created in Thread Stack, the compiled native instruction is executed. After the method exection completes, the CPU instruction is set to the return address.

A common question that might arise would be what happens when a new object is created using the new operator ? During the object creation, the CLR calculates the size of instance fields in the type. It then allocates memory for the calculated size, including fields for Type Object Pointer and Sync Block Index. It also resets the memory to zero. At this point, the constructor is invoked and any fields that could be initialized are assign initial values. It then returns the reference(address) to the newly created object.

Let us now see how CLR handles method execution and how the data structures we discussed in earlier section are related.

Static Method Execution

During the execution of Static Methods, CLR locates the concerned Type’s Type Object data structure in the heap (it would create it if not already created). It then proceed to locate the entry in the method table for the method to be invoked. The compiler then JIT Compiles the method code (only if required – if the method has already been invoked and the native code is already available, this step is skipped) and start executing the native code.

Non Virtual Instance Methods

The execution of a non-virtual instance method is slightly different from the execution of Static Method. It has couple of additional steps.

During the execution of non-virtual instance methods, the compiler detects the Type Object that corresponds to the type of variable being used in the method call. If the method entry is not found in the method table, then it walks through the class heirarchy, checking each of the base type objects for an entry in the method table.

Once it finds the method, follows the steps mentioned for static method. It JIT compiles (only if required) the method code and invokes the native code.

Virtual Instance Method

In the case of virtual instance methods, the compiler begins by refering to the address of instance object the variable points to. The Type object pointer of the instance object would be pointing to the actual type of the object. It then locates the method entry in the Method table, JIT compiles if required and start executing the code.

A summary of the whole process is visualized in the sketch notes below.

Cheat Sheet for Path Markup Syntax

Path Markup Syntax provides a mini-language for describing complex collection of lines and curves. Make no mistake when describing it as mini-language – It is quite powerful and could reduces a lot of nested collection of Xaml elements into a single line.

Having said so, I personally favour the Xaml Element way of constructing elements, mainly due to readability factor. But the language is quite useful in its own ways.

Here is a little cheat sheet for Path Markup Syntax to aid your development cause.

Cheat Sheet Path Markup Syntax

Why be wary of Value Coercion in Dependency Properties

If you are not quite familiar with Value Coercion, it allows you to change/correct value of a property, when it is assigned an unexpected value. This also allows you to ensure relative properties are also kept in sync or in other words, allows you to enforce relation between properties of an object. For example, Date of Birth should not exceed Date of Demise, or Minimum Value in a slider should not exceed the Maximum Value.

You could achieve such a synchronization mechanism using Value Coercion. Consider the following code.

// Note that code has a 'little problem', we will discuss it shortly.
public int MinimumValue
{
    get { return (int)GetValue(MinimumValueProperty); }
    set { SetValue(MinimumValueProperty, value); }
}

public static readonly DependencyProperty MinimumValueProperty = 
    DependencyProperty.Register(nameof(MinimumValue), typeof(int), typeof(Configuration), new PropertyMetadata(0, null, new CoerceValueCallback(OnMinimumValueCoerce)));

private static object OnMinimumValueCoerce(DependencyObject d, object baseValue)
{
    if(d is Configuration config && baseValue is int newValue)
    {
        var maxValue = config.MaximumValue;
        var oldValue = config.MinimumValue;
        return maxValue > newValue ? newValue : oldValue;
    }
    return baseValue;
}

public int MaximumValue
{
    get { return (int)GetValue(MaximumValueProperty); }
    set { SetValue(MaximumValueProperty, value); }
}

public static readonly DependencyProperty MaximumValueProperty =
    DependencyProperty.Register(nameof(MaximumValue), typeof(int), typeof(Configuration), new PropertyMetadata(0,null,new CoerceValueCallback(OnMaximumCoerce)));

private static object OnMaximumCoerce(DependencyObject d, object baseValue)
{
    if(d is Configuration config && baseValue is int newValue)
    {
        var minValue = config.MinimumValue;
        var oldValue = config.MaximumValue;
        return newValue > minValue ? newValue : oldValue;
    }
    return baseValue;
}

In the above code, you are using the CoerceValueCallback within the PropertyMetada to ensure MinimumValue and MaximumValue are kept in sync with each other. Every time a new value is assigned for MinimumValue, it checks if the value is exceeds the MaximumValue. If yes, it reverts the changes and retains the old value.

Little Problem

While the above code looks seemingly harmless, there is a small problem associated with. If you examine the code, we have coercing values for both MinimumValue and Maximum value. Now imagine a scenario when are using the Dependency properties in your code.

<controls:Configuration MinimumValue="{Binding MinValue}"  MaximumValue="{Binding MaxValue}" />

Consider the initial values of MinValue and MaxValue are 1 and 100. At the first glance, this looks valid value, however if you execute the code, you would realize the MinimumValue nevers gets set. Instead, the OnMinimumValueCoerce comes into play and reverts the changes. This is becase, at first the MinimumValue is set (by the order in which is defined in our Xaml – Change the order and behavior would be different).

When a value 1 is assigned to MinimumValue, the OnMinimumValueCoerce notices that it exceeds the current value of MaximumValue (which hasn’t changed yet and is still default of 0). This causes the values to reverts as per our logic in OnMinimumValueCoerce.

PropertyChangedCallback Vs CoerceValueCallback Vs ValidateValueCallback

At this point, it is worth noticing the difference between the 3 seemingly similiar callbacks associated with DependencyProperty – PropertyChangedCallbackCoerceValueCallback and ValidateValueCallback.

The difference could be summarized as

  • PropertyChangedCallback – Reacts to a value change
  • ValidateValueCallback – Determine if the value is valid
  • CoerceValueCallback – Coerce a value.

Order of execution

  1. ValidateValueCallback
  2. CoerceValueCallback
  3. PropertyChangedCallback

Solution

One solution that could be applied in this scenario is

  • Allow MinimumValue to be set to any value
  • In PropertyChangedCallback of MinimumValue, force coercion of MaximumValue.
  • In CoerceValueCallback of MaximumValue, check if it is less than MinimumValue and set it to MinimumValue.

For example,

public int MinimumValue
{
   get { return (int)GetValue(MinimumValueProperty); }
   set { SetValue(MinimumValueProperty, value); }
}

public static readonly DependencyProperty MinimumValueProperty = 
   DependencyProperty.Register(nameof(MinimumValue), typeof(int), typeof(Configuration), new PropertyMetadata(0, new PropertyChangedCallback(OnMinimumValueChanged)));

private static void OnMinimumValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
   if(d is Configuration config)
   {
       config.CoerceValue(MaximumValueProperty);
   }
}

public int MaximumValue
{
   get { return (int)GetValue(MaximumValueProperty); }
   set { SetValue(MaximumValueProperty, value); }
}

public static readonly DependencyProperty MaximumValueProperty =
   DependencyProperty.Register(nameof(MaximumValue), typeof(int), typeof(Configuration), new PropertyMetadata(0,null,new CoerceValueCallback(OnMaximumCoerce)));

private static object OnMaximumCoerce(DependencyObject d, object baseValue)
{
   if(d is Configuration config && baseValue is int newValue)
   {
       var minValue = config.MinimumValue;
       return newValue > minValue ? newValue : minValue;
   }
   return baseValue;
}

As you can see, while 3 muskeeters of PropertyChangedCallbackCoerceValueCallback and ValidateValueCallback are extremly useful, one needs to be aware of similiar issues which could come along and hence one needs to be aware of what is the difference between them and the sequence of execution.

Circular Progressbar in WPF

One of the things I have been working recently required me to use a Circular Progress bar. Incidently, I was surprised there wasn’t something useful in the WPF package, but it wasn’t that hard to do at the hindsight.

The core idea would be to draw two overlapping circles – one for the background circle and other indicating the progress. We will begin by defining certain characterstics of the Cricle (or better called Arc), which we will find useful later on. So, let us first define our model.

public class ProgressArc : PropertyChangedBase
{
    public Point StartPosition { get; set; } = new Point(50, 0);
    public Point EndPosition { get; set; } = new Point(100, 0);
    public Size Radius { get; set; }
    public double Thickness { get; set; } = 2;
    public double Angle { get; set; }
}

Each of the Circle/Arcs are characterized by 5 properties as defined in the Model above.

  • Start Position
  • End Position
  • Radius Of the Arc
  • Thickness of the Stroke
  • Angle

While most of the characterstics are self explanatory, the Angle might need a bit of exaplantion. Let us take a step back and think about what we are attempting to accomplish here. We would like to represent a Value within a Range (Maximum and Minimum Values) just as in regular ProgressBar, but this time as a Circle/Arc. When the Value is equavalent to the Maximum, we would like to see a full circle. But anything short of maximum value (or in other words less than 100%), we would need to draw an arc that ends at an angle, which would represent the percentage of completion. The angle could be calculated pretty easily with the following.

var percent = (currentValue / (maxValue - minValue) * 100);
var valueInAngle = (percent / 100) * 360;

Alright – so with that explanation under our belt, let us proceed to construct our view.

<Viewbox Stretch="Uniform" Margin="10">
    <Grid>
        <Canvas Height="100" Width="100" >
            <Path Stroke="LightGray" StrokeThickness="{Binding BackgroundCircle.Thickness}" >
                <Path.Data>
                    <PathGeometry>
                        <PathFigure  StartPoint="{Binding BackgroundCircle.StartPosition}">
                            <PathFigure.Segments>
                                <ArcSegment RotationAngle="0" SweepDirection="Clockwise"
                                            Size="{Binding BackgroundCircle.Radius}"
                                            IsLargeArc="True"
                                        Point="{Binding BackgroundCircle.EndPosition}"
                                            >

                                </ArcSegment>

                            </PathFigure.Segments>
                        </PathFigure>
                    </PathGeometry>
                </Path.Data>
            </Path>

            <Path Stroke="Blue" StrokeThickness="{Binding ValueCircle.Thickness}" StrokeEndLineCap="Round">
                <Path.Data>
                    <PathGeometry>
                        <PathFigure  StartPoint="{Binding ValueCircle.StartPosition}">
                            <PathFigure.Segments>
                                <ArcSegment RotationAngle="0" SweepDirection="Clockwise"
                                            Size="{Binding ValueCircle.Radius}"
                                            IsLargeArc="{Binding ValueCircle.Angle,Converter={StaticResource AngleToIsLargeConverter}}"
                                        Point="{Binding ValueCircle.EndPosition}"
                                            >

                                </ArcSegment>

                            </PathFigure.Segments>
                        </PathFigure>
                    </PathGeometry>
                </Path.Data>
            </Path>

        </Canvas>
        <TextBlock Text="{Binding CurrentValue}" FontSize="32" VerticalAlignment="Center" HorizontalAlignment="Center" />
    </Grid>
</Viewbox>

Of course, that would look pretty incomplete without our ViewModel. Let us define our ViewModel too before we discuss the finer points.

 public class ShellViewModel : Screen
{
    public ShellViewModel()
    {
        BackgroundCircle.Angle = 360;
        BackgroundCircle.PropertyChanged += OnCircleChanged;
        ValueCircle.PropertyChanged += OnCircleChanged;
        PropertyChanged += OnPropertyChanged; ;
    }

    private void OnPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        switch (e.PropertyName)
        {
            case nameof(MaxValue):
            case nameof(MinValue):
            case nameof(CurrentValue):
            case nameof(SelectedOverlayMode):
                RefreshControl();
                break;
        }
    }

    private void OnCircleChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        RefreshControl();
    }

    protected override void OnViewAttached(object view, object context)
    {
        base.OnViewAttached(view, context);
        RefreshControl();
    }
    private void RefreshControl()
    {
        ArcCalculatorBase arcCalculator;
        switch (SelectedOverlayMode)
        {
            case OverlayMode.Centered:
                arcCalculator = new CenteredArcCalculator(BackgroundCircle.Thickness, ValueCircle.Thickness); 
                break;
            case OverlayMode.InnerCircle:
                arcCalculator = new InsetArcCalculator(BackgroundCircle.Thickness, ValueCircle.Thickness);
                break;
            case OverlayMode.OuterCircle:
                arcCalculator = new OutsetArcCalculator(BackgroundCircle.Thickness, ValueCircle.Thickness);
                break;
            default:
                arcCalculator = new OutsetArcCalculator(BackgroundCircle.Thickness, ValueCircle.Thickness);
                break;
        }
        arcCalculator.Calculate(MinValue, MaxValue, CurrentValue);

        BackgroundCircle.Radius = arcCalculator.BackgroundCircleRadius;
        BackgroundCircle.StartPosition = arcCalculator.BackgroundCircleStartPosition;
        BackgroundCircle.EndPosition = arcCalculator.BackgroundCircleEndPosition;

        ValueCircle.Radius = arcCalculator.ValueCircleRadius;
        ValueCircle.StartPosition = arcCalculator.ValueCircleStartPosition;
        ValueCircle.EndPosition = arcCalculator.ValueCircleEndPosition;
        ValueCircle.Angle = arcCalculator.ValueAngle;
    }

    public ProgressArc BackgroundCircle { get; set; } = new ProgressArc();
    public ProgressArc ValueCircle { get; set; } = new ProgressArc();
    public double MinValue { get; set; } = 10;
    public double MaxValue { get; set; } = 120;
    public double CurrentValue { get; set; } = 60;

    public OverlayMode SelectedOverlayMode { get; set; }
}

The ViewModel defined is pretty straightforward. We have to instances (BackgroundCircle & ValueCircle) of our model (ProgressArc). These would represent our circles. We have few other properties, including the MinValueMaxValue and CurrentValue, which would be used to configure the ProgressBar.

Each time any of our configuration parameter changes( MinValueMaxValue or CurrentValue), we would need to recalculate the dimentions of the arcs. This is being done in the RefreshControl method. The SelectedOverlayMode property defines how the two circles needs to be aligned with respect to each other.

public enum OverlayMode
{
   InnerCircle,
   OuterCircle,
   Centered
}

Depending on the alignment of the two circles, we would need to alter our calculations. These are defined as the following.

 public abstract class ArcCalculatorBase
{
    protected const double ORIGIN = 50;
    protected double _backgroundCircleThickness;
    protected double _valueCircleThickness;
    public ArcCalculatorBase(double backgroundCircleThickness, double valueCircleThickness)
    {
        _backgroundCircleThickness = backgroundCircleThickness;
        _valueCircleThickness = valueCircleThickness;
    }

    public Size BackgroundCircleRadius { get; protected set; }
    public Size ValueCircleRadius { get; protected set; }

    public Point BackgroundCircleStartPosition { get; protected set; }
    public Point BackgroundCircleEndPosition { get; protected set; }

    public Point ValueCircleStartPosition { get; protected set; }
    public Point ValueCircleEndPosition { get; protected set; }

    public double ValueAngle { get; set; }

    protected double GetAngleForValue(double minValue, double maxValue, double currentValue)
    {
        var percent = (currentValue - minValue) * 100 / (maxValue - minValue);
        var valueInAngle = (percent / 100) * 360;
        return valueInAngle;
    }
    public abstract void Calculate(double minValue, double maxValue, double currentValue);
    protected Point GetPointForAngle(Size radiusInSize,double angle)
    {
        var radius = radiusInSize.Height;
        angle = angle == 360 ? 359.99 : angle;
        double angleInRadians = angle * Math.PI / 180;

        double px = ORIGIN + (Math.Sin(angleInRadians) * radius);
        double py = ORIGIN + (-Math.Cos(angleInRadians) * radius);

        return new Point(px, py);
    }
}

public class OutsetArcCalculator : ArcCalculatorBase
{
    public OutsetArcCalculator(double backgroundCircleThickness, double valueCircleThickness):base(backgroundCircleThickness,valueCircleThickness)
    {

    }

    public override void Calculate(double minValue,double maxValue,double currentValue)
    {
        BackgroundCircleRadius =  new Size(ORIGIN - _backgroundCircleThickness / 2, ORIGIN - _backgroundCircleThickness / 2);
        ValueCircleRadius = new Size(ORIGIN - _valueCircleThickness / 2, ORIGIN - _valueCircleThickness / 2); ;

        BackgroundCircleStartPosition = GetPointForAngle(BackgroundCircleRadius, 0);
        BackgroundCircleEndPosition = GetPointForAngle(BackgroundCircleRadius, 360);

        ValueAngle = GetAngleForValue(minValue, maxValue, currentValue);

        ValueCircleStartPosition = GetPointForAngle(ValueCircleRadius, 0);
        ValueCircleEndPosition = GetPointForAngle(ValueCircleRadius, ValueAngle);
    }
}

public class CenteredArcCalculator : ArcCalculatorBase
{
    public CenteredArcCalculator(double backgroundCircleThickness, double valueCircleThickness) : base(backgroundCircleThickness, valueCircleThickness)
    {

    }

    public override void Calculate(double minValue, double maxValue, double currentValue)
    {
        var maxThickness = Math.Max(_backgroundCircleThickness, _valueCircleThickness);
        
        BackgroundCircleRadius = new Size(ORIGIN - maxThickness / 2, ORIGIN - maxThickness / 2);
        ValueCircleRadius = new Size(ORIGIN - maxThickness / 2, ORIGIN - maxThickness / 2); ;

        BackgroundCircleStartPosition = GetPointForAngle(BackgroundCircleRadius, 0);
        BackgroundCircleEndPosition = GetPointForAngle(BackgroundCircleRadius, 360);

        ValueAngle = GetAngleForValue(minValue, maxValue, currentValue);

        ValueCircleStartPosition = GetPointForAngle(ValueCircleRadius, 0);
        ValueCircleEndPosition = GetPointForAngle(ValueCircleRadius, ValueAngle);
    }
}

public class InsetArcCalculator : ArcCalculatorBase
{
    public InsetArcCalculator(double backgroundCircleThickness, double valueCircleThickness) : base(backgroundCircleThickness, valueCircleThickness)
    {

    }

    public override void Calculate(double minValue, double maxValue, double currentValue)
    {
        var maxThickness = Math.Max(_backgroundCircleThickness, _valueCircleThickness);

        BackgroundCircleRadius = new Size((ORIGIN - maxThickness) + (_backgroundCircleThickness / 2), (ORIGIN - maxThickness) + (_backgroundCircleThickness / 2));
        ValueCircleRadius = new Size((ORIGIN - maxThickness) + (_valueCircleThickness / 2), (ORIGIN - maxThickness) + (_valueCircleThickness / 2));

        BackgroundCircleStartPosition = GetPointForAngle(BackgroundCircleRadius, 0);
        BackgroundCircleEndPosition = GetPointForAngle(BackgroundCircleRadius, 360);

        ValueAngle = GetAngleForValue(minValue, maxValue, currentValue);

        ValueCircleStartPosition = GetPointForAngle(ValueCircleRadius, 0);
        ValueCircleEndPosition = GetPointForAngle(ValueCircleRadius, ValueAngle);
    }
}

The GetPointForAngle() converts the given angle to a point on Canvas using the following formulae

Point = (InitialPosition + (Sin(AngleInRadians) * radius),InitialPosition + (-Cos(AngleInRadians) * radius))

The final outcode, based on different Overlay configuration are shown in figures below

Overlay with Equal Thickness
Center Aligned
Inner Circle Aligned
Outer Circle Aligned

The Complete code could found in here in my Github.