Now that we have complete support overriding everything—methods, properties, indexers, events—that can be overridden in a base class or interface, there’s a bit of tidying up to do. In today’s article, we’ll take steps to make base types much more useful by inserting them into their proper place in the type hierarchy.

Table of Contents

Last week we completed support for overriding everything possible to override in a base type, but some of their structure still isn’t right. There are two issues we’ll tackle today.

First, all generated C++ classes for base types extended System::Object instead of their proper base type. This is fine for interfaces, but doesn’t match the C# side when used with base classes. For example, here’s Queue:

namespace System
{
	namespace Collections
	{
		struct Queue : System::Object
		{
			// ... generated contents
		};
	}
}

This causes a problem: we can’t use this Queue type like a normal Queue. For example, we can’t call Clear on it:

void Foo(Queue& queue)
{
	queue.Clear(); // compiler error, Queue doesn't have Clear()
}

This sounds like an easy adjustment. We just need to make Queue extend Queue:

namespace System
{
	namespace Collections
	{
		struct Queue : System::Collections::Queue
		{
			// ... generated contents
		};
	}
}

Of course this won’t work because we can’t have the same class extending itself. We need to disambiguate the real class—Queue—from this base class that game code is supposed to derive from. We can do that easily enough by changing its name to include “Base”:

namespace System
{
	namespace Collections
	{
		struct BaseQueue : System::Collections::Queue
		{
			// ... generated contents
		};
	}
}

Now we have a class that’s properly inserted into the type hierarchy. Polymorphism now works with this type and allows the Clear call to work as expected:

void Foo(BaseQueue& queue)
{
	queue.Clear(); // OK, BaseQueue derives from Queue which has Clear()
}

Game code using base types just needs to derive from the “Base” version now:

struct MyQueue : System::Collections::BaseQueue
{
	// ... override methods, properties, indexers, and events
};

This also has implications on the code generator’s JSON config file. In this example, we’d need two blocks:

{
	"Types": [
		{
			"Name": "System.Collections.Queue",
			"Methods": [
				{
					"Name": "Clear",
					"ParamTypes": []
				}
			]
		}
	],
	"BaseTypes": [
		{
			"Name": "System.Collections.Queue",
			"OverrideProperties": [
				{
					"Name": "Count",
					"Get": {},
					"Set": {}
				}
			]
		}
	]
}

We can’t add to the “BaseTypes” section without also adding to the “Types” section. This can be a little confusing and redundant, so let’s combine the two so it’s easier to configure the code generator:

{
	"Types": [
		{
			"Name": "System.Collections.Queue",
			"Methods": [
				{
					"Name": "Clear",
					"ParamTypes": []
				}
			],
			"BaseTypes": [
				{
					"OverrideProperties": [
						{
							"Name": "Count",
							"Get": {},
							"Set": {}
						}
					]
				}
			]
		}
	]
}

“BaseTypes” is now inside the individual types of the “Types” section. It’s just one more thing to generate for a given type. If we want the ability to derive from the type, we specify a base type there. Because they’re combined, we no longer need a “Name” field. That’s one less string to type and one fewer annoying typo that’ll cause the code generator to error.

With these changes we now have much more useful support for base types. They participate in the type hierarchy now, so we can use them just like regular types. We can use polymorphism to pass them in place of the types they extend and use their full public API. All of this is up on the GitHub project if you want to try it out or see how it was implemented.

Got questions about the project or article series? Post a comment or a GitHub Issue!