fun with assembly attributes (warning: this is a highly technical post)

since installing CrashReporter.net, I noticed that I haven’t really been storing Mysterious Space’s version information correctly. well, or not in a way that Windows really understands. as a result, my reports from CrashReporter.NET keep reporting version “0.0.0.0”.

I wanted to fix this.

and that lead down an exciting rabbit hole!

SO: in my code, I used to have a few variables, like this:

public const int VERSION = "0.7.5";
public const int RELEASE_YEAR = 2015;
public const int RELEASE_MONTH = 5;
public const int RELEASE_DAY = 24;

a single place for me to keep all the version-related info, that I always made sure to update when compiling for release.

at first I thought I wanted a way to get that VERSION into whatever field Windows and CrashReporter.NET were looking at, but I quickly realized this was backwards; what I REALLY wanted was for VERSION to be populated from the field that Windows and CrashReporter.NET look at: Windows keeps a bunch of application metadata in the “assembly”, including version number, copyright date… all that stuff you see when you right-click an executable and get its properties, and you can’t set that from a variable!

you can find all that stuff in your project’s AssemblyInfo.cs file, which has lines like this:

[assembly: AssemblyTitle("Mysterious Space")]

and this:

[assembly: AssemblyVersion("0.0.0.0")]
[assembly: AssemblyFileVersion("0.0.0.0")]

hey, those look like version numbers!

but what’s the difference between the two?

honestly, it’s all still a little confusing to me, but the details mostly seems to matter only when you’re building a library, or something; less so when building a stand-alone application. you can read all about it on StackOverflow.

but I also learned that there’s an AssmeblyInformationVersion, and THIS one is intended for display to users, and is listed in your file properties as “Product version”, which sounded EXACTLY like what I wanted, so: in goes:

[assembly:AssemblyInformationVersion("0.7.5")]

(it’s perhaps worth mentioning that CrashReporter.NET looks to the AssemblyVersion, so I made sure to set that, as well. though P.S. it irks me that I can’t #define VERSION "0.7.5" and then use that in both places, like I would be able to in C++. I want all the things in place! oh, god, did I just express a longing for C++? sorry, sorry. that was a mistake! :P)

at this point, I still wasn’t sure how I was going to get at this version string in the code, but something else was on my mind: “can I add my release date here, as well?”

I COULD have left it in as those const variables, but that bugged me, somehow. I want all this stuff in ONE PLACE, so that when I go to edit, say, the version string, I KNOW I’ll see the date, too, and so be sure to update it accordingly.

unfortunately, there isn’t a built-in field for storing a release date. fortunately, you can add ANYTHING YOUR HEART DESIRES through the use of custom assembly attributes.

you can google around for how to implement those, but here’s what I came up with for Mysterious Space:

using System;

namespace MysteriousSpace
{
    [AttributeUsage(AttributeTargets.Assembly)]
    class BuildDateAttribute: Attribute
    {
        public int Year { get; private set; }
        public int Month { get; private set; }
        public int Day { get; private set; }

        public BuildDateAttribute(int year, int month, int day)
        {
            Year = year;
            Month = month;
            Day = day;
        }
    }
}

which allows me to add, in my AssemblyInfo.cs file:

[assembly:BuildDate(2015, 5, 24)]

awesome!

but NOW: how do we get at that information from within the game code? for example, I display the game’s version and release date in the lower-left of the title menu.

well, the AssemblyInformationalVersion is easy to get at:

string version = Application.ProductVersion;

(you’ll need to be using System.Windows.Forms; to get the Application object.)

getting at custom attributes is a bit trickier, though, and again, in the interest of keeping all this stuff in one place, I created yet another class:

using System.Reflection;
using System.Windows.Forms;

namespace MysteriousSpace
{
    public class BuildInfo
    {
        public string Version { get; private set; }
        public int Year { get; private set; }
        public int Month { get; private set; }
        public int Day { get; private set; }

        public BuildInfo()
        {
            Version = Application.ProductVersion;

            BuildDateAttribute[] attributes = (BuildDateAttribute[])Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(BuildDateAttribute), false);

            Year = attributes[0].Year;
            Month = attributes[0].Month;
            Day = attributes[0].Day;
        }
    }
}

excellent.

now I can just make a new instance of BuildInfo, and I’m good to go. since I’m using MonoGame, I added a public static variable on my main Game object (which I creatively called TheGame), and initialize it before calling Run(). this way, it’s available anywhere:

string releaseDate = TheGame.Build.Year + "-" + TheGame.Build.Month + "-" + TheGame.Build.Day;

ta-da! at last!

as it always is with code, there are many possible solutions to the same problem. I happened to feel strongly that all this information should be in one place, and that lead to the addition of two new classes to my code! overkill? *shrugs* I think it’s cool, and I learned how to do some new stuff. I’m’a call that a win 😛

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s