It's not tail-recursive. After the recursive call comes another iteration of the foreach loop. But, not all hope is lost.
What you can do to convert it to iteration is make a Stack<Tuple<DataGridControl, IEnumerable<CollectionViewGroup>>
and replace the recursive call with pushing a new tuple onto the stack. Then loop until the stack is exhausted.
But first, I notice that the first parameter never changes. So the iterative version doesn't actually need to keep that on the stack:
public void CollapseGroups(DataGridControl grid, IEnumerable<CollectionViewGroup> groups)
{
var to_process = new Stack<IEnumerable<CollectionViewGroup>>();
to_process.Push(groups);
do {
groups = to_process.Pop();
foreach (var @group in groups)
{
grid.CollapseGroup(@group);
to_process.Push(@group.Items.OfType<CollectionViewGroup>());
}
} while (to_process.Count > 0);
}