Q:

How do I get a list of control qubits from Q# operations when tracing the simulation in C#?

I want to write code that prints out the controls of each operation executed during a simulation in Q#. For example this code prints the control counts:
var qsim = new QCTraceSimulator(config);
qsim.OnOperationStart += (op, arg) => {
    Console.WriteLine($"{Controls(op, arg).Length"});
}

I'm having trouble writing the Controls function, which extracts a list of qubits being used as controls. When the operation is uncontrolled, or controlled by 0 qubits, the returned array should be of length 0.
The issue I'm running into is that the type and layout of arg.Value varies from operation to operation, even after conditioning on op.Variant being OperationFunctor.ControlledAdjoint or OperationFunctor.Controlled. I can handle individual cases by inspecting the types, but I keep running into new unhandled cases. This indicates there's probably a "correct" way to do this that I'm missing.
In short, how do I implement this function:
object[] Controls(ICallable op, IApplyData arg) {
    ???
}

By "controls" I always mean the cs in Controlled Op(cs, ...). The same operation may have different controls when expressed in different ways. For example, the controls list of Controlled Toffoli(a, (b, c, d)) is the list [a] whereas the controls list of Controlled X([a, b, c], d) is the list [a, b, c]. A further example: the controls list of Toffoli(b, c, d) is [], even though normally one might think of the first two arguments as the controls. It is of course expected that within Toffoli(b, c, d) there may be a sub-operation Controlled X((b, c), d) where the controls list is [b, c]; I'm not thinking of controls as some kind of absolute concept that is invariant as you go down through layers of abstraction.

A:

arg.Value contains the actual tuple that the controlled operation receives at runtime. It's a two item tuple in which the first item is the control qubits, and the second another tuple with the arguments the operation normally expects, so in your case you are only interested in the first item of this tuple.
Overall, arg.Value can be anything, thus it has object as type, but fear not, using a little bit of C#'s reflection is easy to retrieve its content. The implementation you are looking for is this:
    static Qubit[] Controls(ICallable op, IApplyData arg)
    {
        // Uncontrolled operations have no control qubits.
        if (op.Variant != OperationFunctor.Controlled &&
            op.Variant != OperationFunctor.ControlledAdjoint)
        {
            return new Qubit[0];
        }

        // Get the first item of the (controls, args) tuple.
        dynamic v = arg.Value;
        QArray<Qubit> ctrls = v.Item1;
        return ctrls.ToArray();
    }

Notice the array of Qubits is encapsulated in something called a QArray<Qubit>, QArray is the data structure we use in simulation for all Q# arrays.

