@Moonglum,

Did you get any problem with your code? We tested it and found it work fine. You may add some output in your custom engine to monitor the calculation:

public override void Calculate(CalculationData data)
{
var cell = data.Cell;
var result = cell.Worksheet.Cells.CreateRange(cell.Row, cell.Column, 1, 1).Address;
data.CalculatedValue = result;
Console.WriteLine(data.FunctionName + ": be calculated for " + cell.Name);
}

Executing your main code we got output for the second calculaion as:

TestUDF: be calculated for B1

TestUDF: be calculated for C1

It denotes the custom function has been calculated correctly when needed.

Giving further test for the calculation, we add another line of code:

workbook.Settings.FormulaSettings.EnableCalculationChain = true;
...
worksheet.Cells["C1"].Formula = @"IF(LEN(B1)>1,TestUDF(),0)"; //Nested UDF
worksheet.Cells["D1"].Formula = @"TestUDF()"; //Nested UDF
...
workbook.CalculateFormula(options);
Console.WriteLine("Calc again...");
worksheet.Cells["A1"].Value = "Test";
workbook.CalculateFormula(options);

We can get the output as:

TestUDF: be calculated for D1
Calc again...
TestUDF: be calculated for B1
TestUDF: be calculated for C1

From the output we can confirm those formulas that need to be re-calculated(B1,C1) have been re-calculated and only those formulas have been re-calculated(D1 does not need the calculation so it has not been re-calculated).