From AS3 to C#, Part 12: Generics Wrapup and Annotations
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
- From AS3 to C#, Part 1: Class Basics
- From AS3 to C#, Part 2: Extending Classes and Implementing Interfaces
- From AS3 to C#, Part 3: AS3 Class Parity
- From AS3 to C#, Part 4: Abstract Classes and Functions
- From AS3 to C#, Part 5: Static Classes, Destructors, and Constructor Tricks
- From AS3 to C#, Part 6: Extension Methods and Virtual Functions
- From AS3 to C#, Part 7: Special Functions
- From AS3 to C#, Part 8: More Special Functions
- From AS3 to C#, Part 9: Even More Special Functions
- From AS3 to C#, Part 10: Alternatives to Classes
- From AS3 to C#, Part 11: Generic Classes, Interfaces, Methods, and Delegates
- From AS3 to C#, Part 12: Generics Wrapup and Annotations
- From AS3 to C#, Part 13: Where Everything Goes
- From AS3 to C#, Part 14: Built-in Types and Variables
- From AS3 to C#, Part 15: Loops, Casts, and Operators
- From AS3 to C#, Part 16: Lambdas and Delegates
- From AS3 to C#, Part 17: Conditionals, Exceptions, and Iterators
- From AS3 to C#, Part 18: Resource Allocation and Cleanup
- From AS3 to C#, Part 19: SQL-Style Queries With LINQ
- From AS3 to C#, Part 20: Preprocessor Directives
- From AS3 to C#, Part 21: Unsafe Code
- From AS3 to C#, Part 22: Multi-Threading and Miscellany
- From AS3 to C#, Part 23: Conclusion
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!
Spot a bug? Have a question or suggestion? Post a comment!
#1 by slava on February 5th, 2016 ·
plz
public function call(param:*): void
instead
public function call(T param:*): void
thx.
#2 by jackson on February 5th, 2016 ·
The article explains why I wrote it that way:
The lack of generics support in AS3 means I can’t make a version taking “T” like in C#. The closest AS3 comes to generics are the pseudo-generics employed by
Vector
. For more on how that’s not actually generics either, see my The Four Vector Classes article.#3 by Stan on October 23rd, 2017 ·
public function call(T param:*): void
I suggest you read the above comment again. This line won’t compile… pls fix
#4 by jackson on October 23rd, 2017 ·
I seem to have missed the point of the original comment. I didn’t realize it was just a typo. I’ve fixed it now. Thanks for pointing this out!
#5 by Andrey on April 8th, 2017 ·
How to reach metadata vars in the code?
Or, generally, what is usage of metadata vars?
#6 by jackson on April 8th, 2017 ·
First get a
Type
reference using eithertypeof(MyClass)
orsomeObject.GetType()
. Then call itsGetCustomAttributes
to get an array of the attributes on the class. Likewise you can get the fields, properties, methods, etc. of the class and get attributes from those. Check out the documentation for more info.One word of caution- this is all quite slow so you should use it sparingly. For example, it’s usually fine at startup on during loading screens, but you wouldn’t want to do this during fast-paced gameplay.