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.