Patching
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.
It is a very good candidate for code that has to run regardless of what happens. Its counterpart is a Prefix with no side effects (void return type and no ref/out arguments). These are never skipped and thus serve as a way to run code guaranteed at the start of a method.
Finalizers are commonly used to:
- suppress exceptions
- remap exceptions
- make sure your code is always executed
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
Finalizers are ideal for cleanup or resource management logic that must execute regardless of success or failure - similar to a finally block in standard C#.
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)
}
}
Beside their handling of exceptions they can receive the same arguments as Postfixes.