Interfaces
Mutagen exposes a few categories of interfaces:
- Getters and Setters (Immutable vs Mutable)
- Aspect (Expose aspects common to many records)
- Link (Enables FormLinks to point to an umbrella of record types)
Getters and Setters
Each autogenerated class also gets accompanying Getter/Setter interfaces
public class Potion : IPotion
{
public int Value { get; set; }
public float Weight { get; set; }
public string Name { get; set; }
}
public interface IPotionGetter
{
int Value { get; }
float Weight { get; }
string Name { get; }
}
public interface IPotion : IPotionGetter
{
int Value { get; set; }
float Weight { get; set; }
string Name { get; set; }
}
These are created mainly to expose objects in a readonly manner when appropriate. The Getter interfaces, specifically, are heavily used when reading data from a mod on-disk.
Aspect Interfaces
Aspect Interfaces expose common fields that are shared among many records. They are used to help write code that is more generic and can process more than just a single record type.
Problem to Solve
public static void PrintName(Armor armor)
{
System.Console.WriteLine($"Found {armor.GetType()} with name: {armor.Name}");
}
Name
? What type do you specify as the parameter?
This is where Aspect Interfaces come to the rescue. An interface is defined:
public interface INamed
{
string Name { get; set; }
}
Name
can implement. Now we can tweak our PrintName
function to apply to any of these records, no matter what they are.
public static void PrintName(INamed name)
{
System.Console.WriteLine($"Found {name.GetType()} with name: {name.Name}");
}
Typical Usage
Using Aspect Interfaces as a Parameter
In the problem described above, a common use case is wanting to write a function that applies to multiple record types:
public static void PrintName(INamedGetter namedRecord)
{
System.Console.WriteLine($"Found name: {namedRecord.Name}");
}
INamed
, we have written a function that can apply to any record that has a name. Could be an Armor, Npc, Weapon, etc.
Weaving Multiple Aspect Interfaces Using Generics
The above example using one Aspect Interface as the parameter type is great if you're only interested in a single aspect.
What if you wanted to write a function that could process any record that had a name, and had a model? Well, then we need to combine multiple aspects.
public static void PrintNameAndModel<TRecord>(TRecord record)
where TRecord : IMajorRecordCommon, INamed, IModeled
{
System.Console.WriteLine($"{record.FormKey} with EditorID {record.EditorID} had name: {record.Name}.");
if (record.Model != null)
{
System.Console.WriteLine($" Model file: {record.Model.File}");
}
}
This function uses C# Generics to specify that TRecord
should be any record that is a Major Record, is named, and has a model.
Now this function can directly access those aspects of records that are passed in. Additionally, this function will automatically only accept records that have all these aspects. A Footstep
record, for example, which isn't named and doesn't have a model, would not be allowed to be passed to this function: C# would give a compiler error.
Evolution
The list of Aspect Interfaces is an evolution. Many more Aspect Interfaces could be created to expose fields that are common to many records.
Additionally, Aspect Interfaces can be made to apply to cross-game records. These of course will be more limited, as concepts that are shared across different games are less than those shared within the same game.
New Interfaces
If you see an Aspect Interface that would be nice to add, please create an issue and ask for it to be added! Over time, the list of Aspect Interfaces will be more and more complete.
Link Interfaces
Link Interfaces have a different goal. They act more as markers, rather than a vehicle to expose common fields.
Problem to Solve
Mutagen's FormLinks can specify a record type that the FormKey they represent is allowed to match against. It provides type safety to FormID/FormKey concepts.
What happens when a FormLink should be able to point to multiple record types? A container object can contain many different record types: Armor, Weapons, etc.
This is where Link Interfaces come into play. A LinkInterface IItem
is defined:
public interface IItem
{
}
FormLink<IItemGetter>
. This link can now point to any of those records that are marked with IItem
Typical Usage
Knowing Types Allowed into a FormLink
Typical interaction with Link Interfaces are when users try to assign a record to a FormLink, and the compiler tells them it's not allowed. They offer type safety, and block users from messing up and assigning a Potion
to an Npc's Race
field.
However, you may want to know what types are allowed when you see a FormLink<IConstructibleGetter>
. There are two ways to get at this information:
Visual Studio Intellisense
Visual Studio offers a lot of tooling to be able to investigate classes/interfaces.
Here is an example of F12
being used to investigate IConstructibleGetter
and see the comments on which objects implement it:
Evolution
New Link Interfaces are rarely defined. These are usually known ahead of time and new ones aren't added.
However, over time they can/could take on more of an Aspect Interface role, where common fields that are shared by all records implementing a Link Interface could be listed. This would be Link Interfaces becoming more than just a simple marker, and becoming their own Aspect Interfaces of sorts.
Field Exposure
If there are some fields common to all records of a Link Interface that you think should be exposed, please make an issue!
List of Interfaces
Mutagen autogenerates some documentation on what Aspect/Link interfaces exist, and what implements what.