Browse documentation

Finalizer

A finalizer is a method that makes Harmony wrap the original and all other patches in a try/catch block. It can receive a thrown exception and even suppress it or return a different one.

Unlike a postfix, it also runs when a prefix, the original, or a postfix throws.

Finalizers are commonly used to:

  • suppress exceptions
  • remap exceptions
  • run cleanup on success or failure

See the runtime flow for how prefixes, postfixes, and finalizers fit together.

Suppressing any exceptions

To suppress all exceptions, return null from a finalizer with return type Exception. This prevents any exception from being rethrown.

public class OriginalCode
{
    public void MightFail()
    {
        throw new Exception("fail");
    }
}

[HarmonyPatch(typeof(OriginalCode), nameof(OriginalCode.MightFail))]
class Patch
{
    static Exception Finalizer()
    {
        return null; // suppresses all exceptions
    }
}

Observing exceptions

To observe an exception without altering it, use a void finalizer with Exception __exception as a parameter. The special __exception parameter will be null if no exception occurred.

public class OriginalCode
{
    public void MightFail()
    {
        throw new Exception("fail");
    }
}

[HarmonyPatch(typeof(OriginalCode), nameof(OriginalCode.MightFail))]
class Patch
{
    static void Finalizer(Exception __exception)
    {
        if (__exception is not null)
            FileLog.Log("caught exception: " + __exception);
    }
}

Changing and rethrowing exceptions

To remap exceptions, return a new exception from the finalizer. This replaces the original exception with a new one.

public class MyException : Exception
{
    public MyException(string message, Exception innerException) : base(message, innerException) { }
}

public class OriginalCode
{
    public void MightFail()
    {
        throw new InvalidOperationException("something went wrong");
    }
}

[HarmonyPatch(typeof(OriginalCode), nameof(OriginalCode.MightFail))]
class Patch
{
    static Exception Finalizer(Exception __exception)
    {
        return __exception is not null ? new MyException("wrapped", __exception) : null;
    }
}

Running cleanup code

Use a finalizer for cleanup that must run on success or failure. A finalizer can run again if one of the finalizers throws, so account for that when releasing resources. See finalizer execution.

public class OriginalCode
{
    public static StreamWriter sharedWriter;

    public void WriteData(string data)
    {
        sharedWriter.Write(data);
    }
}

[HarmonyPatch(typeof(OriginalCode), nameof(OriginalCode.WriteData))]
class Patch
{
    static Exception Finalizer(Exception __exception)
    {
        OriginalCode.sharedWriter?.Flush(); // always flush, even if an exception occurred
        return __exception; // rethrow the original exception (if any)
    }
}

Finalizers can receive the same injected arguments as postfixes, plus __exception.

Harmony 3 preview

For the stable release, read the 2.x documentation.