SharpCell

Custom functions

Functions written in C# that formulas call by name, in place of VBA functions or add-ins that a workbook expects.

Adding a function

workbook.Functions.Add("VAT", args =>
{
    CellValue amount = args.Number(0);   // converted as Excel converts: "100" is 100
    if (amount.IsError)
        return amount;                  // pass #VALUE! and other errors on
    return amount.AsNumber() * 0.2;
}, new FunctionOptions { MinArguments = 1, MaxArguments = 1, ScalarParameters = [0] });

sheet["A1"].Value = 100;
sheet["A2"].Value = 250;
sheet["B1"].Formula = "=VAT(A1)";      // 20
sheet["C1"].Formula = "=VAT(A1:A2)";   // a scalar parameter given a range: spills 20 and 50
workbook.Recalculate();

Workbook.Functions.Add takes a name, a body and options. Formulas call the function ignoring case, and can pass it as a value where a LAMBDA is expected, as in MAP(A1:A9, VAT). Formulas that were #NAME? because the function was missing are calculated again by the next Recalculate; Remove turns its calls back into #NAME?. Each workbook has its own functions, and Workbook.Clone copies them.

The name has letters, digits, . and _, and starts with a letter or _. The name of a function SharpCell implements is refused: their results are checked against Excel. A name Excel knows but SharpCell does not implement, such as WEBSERVICE, can be added. In files, Excel writes calls of add-in functions as _xll.NAME(...) and calls of functions it does not know as _xludf.NAME(...); both find the function NAME.

Arguments

Option Meaning
MinArguments, MaxArguments How many arguments a call may have, 0 to 255. A call outside the range is #VALUE!, as with Excel's functions.
ScalarParameters Positions (from 0) of parameters that take one value. Given a range or an array, the function runs once per element and the results spill, as LEN(A1:A10) does.
IsVolatile The function runs on every Recalculate, like NOW, not only when its arguments change.

FunctionArguments gives the body the call's arguments:

A parameter that is not scalar receives a range as an array of its values:

workbook.Functions.Add("SUMSQUARES", args =>
{
    double total = 0;
    foreach (CellValue value in args[0].Kind == CellValueKind.Array ? args[0].AsArray() : new[,] { { args[0] } })
    {
        if (value.Kind == CellValueKind.Number)
            total += value.AsNumber() * value.AsNumber();
    }

    return total;
}, new FunctionOptions { MinArguments = 1, MaxArguments = 1 });

sheet["B1"].Formula = "=SUMSQUARES(A1:A3)";   // the range arrives as an array: 25

Results and failures

The body returns a CellValue. An array result spills like any dynamic array. Missing and lambda results are #VALUE!.

An exception thrown by the body makes the cell #VALUE! and adds a diagnostic of kind DiagnosticKind.CustomFunctionFailure, naming the function and the exception. The rest of the workbook calculates as usual. An OperationCanceledException cancels the calculation only when its token was cancelled; otherwise it is a failure like any other exception.

Rules for the body

A workbook whose formulas use custom functions saves with XlsxWriter.Save like any other: the results are calculated by SharpCell, so KeepUncalculated is not needed.