Most of C#’s support for generics was covered in the previous article, but today we’ll wrap it up by discussing covariance and contravariance. We’ll also talk about C#’s support for annotations and compare to what’s available in AS3.

Table of Contents

First let’s wrap up generics by discussing covariance and contravariance. Covariance is when you can assign something of a more derived type to something of a less derived type. For example, you can assign a String to an Object variable. Contravariance is when you can assign something of a less derived type to something of a more derived type. It’d be like you could assign an Object to a String.

We can specify covariance and contravariance using the out and in modifier keywords on our interfaces’ type parameters. Here’s how covariance looks:

// Modify T with the out keyword to make it covariant
public interface IContainer<out T>
{
	// Use T like normal
	T getValue();
}
 
// Implementing the interface doesn't require you to use 'out' again
public class MyContainer<T> : IContainer<T>
{
	public T val;
 
	public MyContainer(T val)
	{
		this.val = val;
	}
 
	public T getValue()
	{
		return val;
	}
}
 
// Use Object for the type parameter
IContainer<Object> objContainer = new MyContainer<Object>(null);
 
// Use String for the type parameter
IContainer<String> strContainer = new MyContainer<String>("hello");
 
// Can assign from a more derived type to a less derived type- covariance
objContainer = strContainer;
 
// Output:
// hello
Debug.Log(objContainer.getValue());

There is one restriction to covariance to be aware of. You can only return the type parameter from functions, not take it as a parameter.

To use contravariance, use the in keyword instead of out:

// Use the 'in' keyword before the type parameter
public interface ICallback<in T>
{
	void call(T param);
}
 
// T is declared without the 'in' keyword in implementing classes
public class MyCallback<T> : ICallback<T>
{
	public void call(T param)
	{
		Debug.Log("called, param: " + param);
	}
}
 
// Use Object for the type parameter
ICallback<Object> objCallback = new MyCallback<Object>();
 
// Use String for the type parameter
ICallback<String> strCallback = new MyCallback<String>();
 
// Assign from Object to String- contravariance
strCallback = objCallback;
 
// Output:
// called, param: hello
strCallback.call("hello");

Like covariance, contravariance has one restriction. You can only take parameters to functions with the contravariant type. You can never return them.

Covariance and contravariance—together just “variance”— aren’t explicitly supported in AS3 since it doesn’t have generics at all, but the usual workaround implicitly supports it. By using * or Object instead of the actual type, you can use basically any type. It’s error-prone since you have no control over what types are used, but the closest AS3 comes to supporting covariance and contravariance. Here’s a brief look at the AS3 workaround using * to allow any type, regardless of inheritance.

public interface ICallback
{
	function call(param:*): void;
}
 
public class MyCallback implements ICallback
{
	public function call(param:*): void
	{
		trace("called, param: " + param);
	}
}

Next up are annotations. AS3 actually has these, so the comparison is much closer than with generics. In C# they’re called attributes and start by defining a class extending System.Attribute:

public class Version : System.Attribute
{
	public int major;
	public int minor;
 
	public Version(int major, int minor)
	{
		this.major = major;
		this.minor = minor;
	}
}

The parameters to the constructor will be passed when you declare an attribute. Here’s how that looks on a variety of different class members:

[Version(1, 2)]
public class MyClass
{
	[Version(1, 2)]
	public void MyFunction()
	{
	}
 
	[Version(1, 2)]
	public int MyVariable;
 
	[Version(1, 2)]
	public int MyProperty { get; set; }
 
	[Version(1, 2)]
	public delegate void MyDelegate();
 
	[Version(1, 2)]
	public event MyDelegate MyEvent;
}

If you want, you can restrict where your attribute is used using yet-another attribute: System.AttributeUsage. Here’s an example:

// Version can only be used on classes and structs now
[System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct)]
public class Version : System.Attribute
{
	// ...
}

You can also allow multiple attributes of the same type on the same item:

[System.AttributeUsage(
	System.AttributeTargets.Class | System.AttributeTargets.Struct,
	AllowMultiple = true
)]
public class Version : System.Attribute
{
	// ...
}
 
// Use the attribute, e.g. to indicate all the versions the class is in
[Version(1, 0)]
[Version(1, 1)]
[Version(1, 2)]
public class MyClass
{
}

AS3 annotations are similar, but lack the strong typing you get by defining your own Attribute class. The benefit is the same: you don’t have to define your own attribute class. You can’t restrict them to only certain types, either. For full details, check out this article. In the meantime, here’s a brief example:

[Version(major=1, minor=2)]
public class MyClass
{
}

To summarize today’s topics, here’s a quick comparison between C# and AS3 for covariance, contravariance, and annotations/attributes:

////////
// C# //
////////
 
// Covariant interface
public interface IContainer<out T>
{
	// Use T like normal
	T getValue();
}
 
// Implement a covariant interface
public class MyContainer<T> : IContainer<T>
{
	public T val;
 
	public MyContainer(T val)
	{
		this.val = val;
	}
 
	public T getValue()
	{
		return val;
	}
}
 
 
// Contravariant interface
public interface ICallback<in T>
{
	void call(T param);
}
 
// Implement a contravariant interface
public class MyCallback<T> : ICallback<T>
{
	public void call(T param)
	{
		Debug.Log("called, param: " + param);
	}
}
 
 
// Define an attribute/annotation
public class Version : System.Attribute
{
	public int major;
	public int minor;
 
	public Version(int major, int minor)
	{
		this.major = major;
		this.minor = minor;
	}
}
 
// Use an attribute/annotation on a class
[Version(1, 2)]
public class MyClass
{
	// Use an attribute/annotation on a function
	[Version(1, 2)]
	public void MyFunction()
	{
	}
 
	// Use an attribute/annotation on a variable
	[Version(1, 2)]
	public int MyVariable;
 
	// Use an attribute/annotation on a property
	[Version(1, 2)]
	public int MyProperty { get; set; }
 
	// Use an attribute/annotation on a delegate
	[Version(1, 2)]
	public delegate void MyDelegate();
 
	// Use an attribute/annotation on an event
	[Version(1, 2)]
	public event MyDelegate MyEvent;
}
/////////
// AS3 //
/////////
 
// Covariant interface
// {impossible in AS3}
 
 
 
 
 
// Implement a covariant interface
public class MyContainer implements IContainer
{
	// Use * instead of strong typing
	public var val:*;
 
	public function MyContainer(val:*)
	{
		this.val = val;
	}
 
	public function getValue(): *
	{
		return val;
	}
}
 
// Contravariant interface
// {impossible in AS3}
 
 
 
 
// Implement a contravariant interface
public class MyCallback implements ICallback
{
	// Use * instead of strong typing
	public function call(param:*): void
	{
		trace("called, param: " + param);
	}
}
 
// Define an attribute/annotation
// {unnecessary/unavailable in AS3}
 
 
 
 
 
 
 
 
 
 
 
// Use an attribute/annotation on a class
[Version(major=1, minor=2)]
public class MyClass
{
	// Use an attribute/annotation on a function
	[Version(major=1, minor=2)]
	public function MyFunction(): void
	{
	}
 
	// Use an attribute/annotation on a variable
	[Version(major=1, minor=2)]
	public var MyVariable:int;
 
	// Use an attribute/annotation on a property
	// {unavailable in AS3}
 
 
	// Use an attribute/annotation on a delegate
	// {unavailable in AS3}
 
 
	// Use an attribute/annotation on an event
	// {unavailable in AS3}
 
}

We’re very close to finishing up C#’s object model (classes, structures, etc.). Next week we’ll tie up some loose ends before moving on to the remainder of the syntax (types, casts, etc.). Stay tuned!

Continue to Part 13

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