From AS3 to C#, Part 6: Extension Methods and Virtual Functions

Datetime:2016-08-23 02:55:48          Topic: ActionScript  C#           Share

Today’s article continues from thelast two in discussing features of C# classes that AS3 doesn’t have. We’ll discuss extension methods and the virtual function system that trips up so many C# newcomers.

Table of Contents

In thelast article we discussed static classes and saw this example of a pair of conversion functions between degrees and radians:

public static class MathHelpers
{
	public const double DegreesToRadians = Math.PI / 180.0;
	public const double RadiansToDegrees = 180.0 / Math.PI;
 
	public static double ConvertDegreesToRadians(double degrees)
	{
		return degrees * DegreesToRadians;
	}
 
	public static double ConvertRadiansToDegrees(double radians)
	{
		return radians * RadiansToDegrees;
	}
}
 
double degrees = 180;
double radians = MathHelpers.ConvertDegreesToRadians(degrees); // 3.14...

Another way this could have been implemented is to add a pair of “extension methods” to the double type. This requires a static class with function taking a special this parameter like so:

public static class DoubleExtensions
{
	public const double DegreesToRadians = Math.PI / 180.0;
	public const double RadiansToDegrees = 180.0 / Math.PI;
 
	public static double ConvertDegreesToRadians(this double degrees)
	{
		return degrees * DegreesToRadians;
	}
 
	public static double ConvertRadiansToDegrees(this double radians)
	{
		return radians * RadiansToDegrees;
	}
}
 
double degrees = 180;
double radians = degrees.ConvertDegreesToRadians(); // 3.14...

Extension methods are purely syntax sugar. When you write this:

degrees.ConvertDegreesToRadians()

The compiler just transforms it into this:

DoubleExtensions.ConvertDegreesToRadians(degrees)

That means that your extension methods aren’t really part of the class, so they can’t access private variables, functions, or properties. They also can’t add variable fields or properties, only functions. Still, some programmers will prefer the aesthetics of extension methods since they make the functions you “add on” to the class look just like the functions the class had to begin with. Others will object to them as it can be more difficult to track down where the method is implemented.

There is one “gotcha” to avoid with extension methods. They aren’t available to your class if it’s not in the same namespace as the class with the extension methods and you don’t have a using statement for that namespace either. For example:

//////////////////////
// StringExtensions.cs
//////////////////////
namespace MyLib
{
	public static class StringExtensions
	{
		public static bool IsNullOrEmpty(this String str)
		{
			return str == null || str.Length == 0;
		}
	}
}
 
/////////////////
// Application.cs
/////////////////
namespace MyApp
{
	public class Application
	{
		public Application(String name)
		{
			// Compiler error because IsNullOrEmpty is in a
			// namespace that isn't visible
			if (name.IsNullOrEmpty())
			{
				throw new Exception("Name can't be empty");
			}
		}
	}
}

The Application class would need to add a using MyLib; statement in order to have visibility to the IsNullOrEmpty extension method.

In AS3, there are no extension methods. Static “helper” functions are usually used instead, perhaps in a class whose constructor always throws an exception to mimic static classes at run-time:

public class NumberHelpers
{
	public static const DEGREES_TO_RADIANS:Number = Math.PI / 180.0;
	public static const RADIANS_TO_DEGREES:Number = 180.0 / Math.PI;
 
	public function NumberHelpers()
	{
		throw new Error("NumberHelpers is static");
	}
 
	public static function convertDegreesToRadians(degrees:Number): Number
	{
		return degrees * DEGREES_TO_RADIANS;
	}
 
	public static function convertRadiansToDegrees(radians:Number): Number
	{
		return radians * RADIANS_TO_DEGREES;
	}
}
 
var degrees:Number = 180;
var radians:Number = NumberHelpers.convertDegreesToRadians(degrees); // 3.14...

Next, let’s discuss “virtual functions”. This is only a new concept to AS3 programmers because all non- final function in AS3 are “virtual”. C# changes this by allowing “non-virtual” functions.

So what is a “virtual function”? It’s simply a function that can be overridden by a deriving class in the way that AS3 works by default. Here’s an example:

public class Shape
{
	// Note: virtual keyword used to make this a virtual function
	public virtual String PrintDescription()
	{
		return "Shape";
	}
}
 
public class Circle : Shape
{
	private double radius;
 
	public Circle(double radius)
	{
		this.radius = radius;
	}
 
	// Note: override keyword used to override Shape's version
	public override String PrintDescription()
	{
		return "Circle [radius=" + radius + "]";
	}
}
 
Circle circle = new Circle(10);
circle.PrintDescription(); // "Circle [radius=10]"
 
Shape shape = new Circle(10);
shape.PrintDescription(); // "Circle [radius=10]"

Note that the virtual keyword was added to the PrintDescription function in Shape . Without this keyword, we would have received a compiler warning on the PrintDescription function in Circle . This is because a non-virtual function is not expecting to be overridden. We’ll see why that is by overriding the compiler warning by adding the new keyword to the PrintDescription function in Circle :

public class Shape
{
	// Note: virtual keyword NOT used. This is a non-virtual function.
	public String PrintDescription()
	{
		return "Shape";
	}
}
 
public class Circle : Shape
{
	private double radius;
 
	public Circle(double radius)
	{
		this.radius = radius;
	}
 
	// Note: override keyword NOT used to override Shape's version
	//       new keyword used to make our own version
	public new String PrintDescription()
	{
		return "Circle [radius=" + radius + "]";
	}
}
 
Circle circle = new Circle(10);
circle.PrintDescription(); // "Circle [radius=10]"
 
Shape shape = new Circle(10);
shape.PrintDescription(); // "Shape"

The big difference here is right at the end where we use a variable of type Shape and call its PrintDescription . Since the PrintDescription function is non-virtual in Shape , derived classes can’t override it. The PrintDescription in Circle is not considered, even if the actual Shape instance is a Circle .

Essentially, the new keyword has created a new version of PrintDescription that is similar to an overloaded function. When we call Shape.PrintDescription we are calling a different function than when we call Circle.PrintDescription .

Note that this is the default behavior in C# and that you need to add the virtual keyword to return to the AS3 way. Because of this, many programmers new to C# are surprised at their first compiler warning when overriding a non-virtual function. The warning is there to alert you that your overriding will not work in the way you’d expect with virtual functions and you should add the new keyword essentially to waive this warning.

This concludes today’s topics but we’ll summarize with a comparison between C# and AS3 covering extension methods and virtual functions from this article:

////////
// C# //
////////
 
public static class StringExtensions
{
 
 
 
 
 
	public static bool IsNullOrEmpty(this String str)
	{
		return str == null || str.Length == 0;
	}
}
 
public class Shape
{
	private static int NextID = 1;
 
	private int id;
 
	public Shape()
	{
		id = NextID++;
	}
 
	// virtual function
	public virtual String PrintDescription()
	{
		return "Shape";
	}
 
 
	// non-virtual function
	public int GetID()
	{
		return id;
	}
}
 
public class Circle : Shape
{
	private double radius;
 
	public Circle(double radius)
	{
		this.radius = radius;
	}
 
	// function overriding a virtual function
	public override String PrintDescription()
	{
		return "Circle [radius=" + radius + "]";
	}
 
	// function with the same name as a base class function
	// but does not override the base class function
	public new int GetID()
	{
		return 0;
	}
}
/////////
// AS3 //
/////////
 
public class StringExtensions
{
	public function StringExtensions()
	{
		throw new Error("StringExtensions is static");
	}
 
	public static function IsNullOrEmpty(str:String): Boolean
	{
		return str == null || str.length == 0;
	}
}
 
public class Shape
{
	private static var NEXT_ID:int = 1;
 
	private var id:int;
 
	public Shape()
	{
		id = NEXT_ID++;
	}
 
	// virtual function
	public function PrintDescription(): String
	{
		return "Shape";
	}
 
	// non-virtual function
	// MUST be final in AS3
	public final function GetID(): int
	{
		return id;
	}
}
 
public class Circle extends Shape
{
	private var radius:Number;
 
	public function Circle(radius:Number)
	{
		this.radius = radius;
	}
 
	// function overriding a virtual function
	public override function PrintDescription(): String
	{
		return "Circle [radius=" + radius + "]";
	}
 
	// function with the same name as a base class function
	// but does not override the base class function
	// {impossible in AS3}
 
 
 
}

Next week’s article will continue discussing the C# class system with even more features that aren’t in AS3. Stay tuned!

Continue to Part 7

Spot a bug? Have a question or suggestion? Post a!





About List