Its not really a pretty solution (it works in most cases), what I did is get the origin point of the beam geometry, then generated a line from that:
// Get origin point of transformed beam geometry
XYZ origin = instance.GetTransform().Origin;
// Offset the origin point vertically by 50 units
XYZ originOffsetZ = new XYZ(origin.X, origin.Y, (origin.Z + 50));
// Create a line between the origin and origin offset point
Line originIntersect = Line.CreateBound(origin, originOffsetZ);
// Determine if top most face by checking if the line created between
// origin point and offset origin point intersects with the face
IntersectionResultArray results;
SetComparisonResult result
= f.Intersect(originIntersect, out results);
if (result == SetComparisonResult.Overlap)
{
TopFace = f;
}
Where this will fail is if your beam start or end extension (or Start / End Join Cutback) is so great that the origin point is no longer within the beam geometry. Also this method will fail if you rotate the beam cross section. So it's not foolproof.
The likelyhood of this kind of modification to the geometry is pretty low, and I have not come across an instance where it has failed in practice.
Here is an example where it won't work (the analytical line is shown to demonstrate what the original beam geometry was before modifying the Start Join Cutback value)

There's most likely a much better way of doing this, that will find the face I'm looking for in every case. But I have not been able to figure it out.
I was maybe thinking of analyzing the "original" beam geometry and somehow getting a unique id for the top face, and then from that id being able to use it to work with the transformed geometry face.
I'm hesitant of saying this is really a "solution". I would love to find a way of being able to get the face I need every time, no matter how the geometry has been manipulated.