Yesterday, I found the source code to my Template Exporter and posted it to CodePlex. The tool allows you to create project templates from your own XNA game projects in Visual Studio. The tool is necessary because the Export Template command built into Visual Studio doesn’t support several features used in XNA game projects, and it isn’t extensible like most other stuff in Visual Studio (that means we can’t modify the command to work properly in the Game Studio code).
One reason I posted the source code is because somebody asked me to do it last year. Something else that person asked me to do was to fix a bug in the Template Exporter. I didn’t pay much attention to the request at the time, because I’d lost the source code, but now that I look at it again, I don’t fully agree with the requested bug fix. Here’s the important part of the request.
Every time a create a project by a template generated by your tool, the startup class name is wrong. And I must change this tag:
<StartupObject>GameTemplate.Game</StartupObject>
To:
<StartupObject>$safeprojectname$.Game</StartupObject>
In this scenario, the project being exported has explicitly set its Startup Object property, which identifies the startup class by its full name. When the project is exported, the root namespace is “templatized” so that when new projects are created, the default namespace matches the new project name, and all the classes within the project are put in that namespace. That means the Game class is in a different namespace each time a new project is created, but the Startup Object property is always set to “GameTemplate.Game”. The request is that, if the startup object property is set, the template exporter should replace it so the default namespace substring is templatized.
Okay, I can see that being a useful thing to do. However, for this particular problem, I don’t think it’s the best fix. The best fix is to remove the StartupObject property from the original project before exporting it in the first place. The rest of this post explains why it’s better to remove it than to have the property set to the correct value.
Why not specify Startup Object?
You might be wondering, “If matching the full name of the Game class will work, then why is removing the property better than matching the full name?” Great question! I’ll answer two other questions first:
- What is the Startup Object property for?
- How does having it set differ from not having it set?
What is the Startup Object property for?
When the Startup Object property is set in a C# project, its value is passed to the C# compiler via the “/main” command line option. When the Startup Object property is not set, the “/main” option is omitted. When the compiler does not see “/main”, it evaluates the source code of the executable to look for a suitable entry point method. If there is only one suitable method, then that becomes the implicit entry point.
However, when the compiler sees more than one suitable entry point method and the “/main” option is not specified, then the compiler will emit the following error:
error CS5001: Program ‘test.exe’ does not contain a static ‘Main’ method suitable for an entry point
You may also get warnings for methods named “Main” that don’t match the entry point signature requirements (a static method named “Main” that returns either int or void and taking either zero arguments or one argument of type string[]).
In the case where the is more than one suitable ‘Main’ method, the Startup Object must be specified in order for the project to compile.
How does having it set differ from not having it set?
Now, the difference between having it set and not set is that when it is not set, the compiler does more work and you do less work. When you let the compiler look for a unique Main entry point, the compiler will find it even if you rename the class, or delete the method and move it to another class, or delete the class and add a new one with a completely different Main method.
By contrast, if you explicitly set the Startup Object property, then you need to remember to change it whenever you do any of those things (rename the class, delete the method and move it to another class, or delete the class and add a new one with a completely different Main method). If you don’t manually keep the Startup Object property synchronized with your code, then the compiler will complain with an error like the following:
error CS1555: Could not find ‘GameTemplate.Game’ specified for Main method
Why does any of this matter?
Setting the Startup Object is useful only to disambiguate between multiple possible entry point methods. If you don’t have multiple Main methods, then setting the Startup Object makes your project less maintainable. That is, some changes become harder to make; either requiring you to know more or do more to accomplish the same task as a project that doesn’t have the Startup Object set.
If there are two Main methods, then you don’t have a choice and you must specify the entry point. Or do you? The compiler can’t deal with ambiguity, but as the programmer who wrote the code, you have another option: rename one of the methods so that the compiler can infer the one and only entry point.
In the vast universe of possibility, there may be a situation where you absolutely must have two Main methods and cannot rename one of them for some bizarre reason. But if you are writing a template, then the only way your end-users could require two Main methods in their as-yet-uncreated projects is if you design the template poorly.
Conclusion
This was all a very long-winded way of explaining why the bug reported to me was better addressed by working around the problem described. That is, in my opinion, you should never define StartupObject in your custom project templates. There’s no good reason for a brand-new project to have an ambiguous entry point. It just makes the project less maintainable for end-users.
P.S. I fixed the bug anyway.