Skip to content

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}");
}
This works great! For armors only. What if you wanted this function to work on any record that had 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; }
}
This is an interface that any record that contains a 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}");
}
Using the Aspect Interface 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 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
{
}
Notice it doesn't have any fields. It is just a marker that Armor, Weapons, etc can implement. Now FormLink can target that interface instead: FormLink<IItemGetter>. This link can now point to any of those records that are marked with IItem

Typical Usage

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.

List of Interfaces