Ideally this would be achieved with a linked list, the advantage is that only node references are disconnected and no internal copying is needed. A C# Queue is actually based a linked list, so this is a fairly appropriate data structure.
However, this can also be done with a List at a small cost. When you do an Insert or Remove, it internally calls Array.Copy and although it's still O(n), providing it doesn't fail a capacity check there is no allocation and is extremely efficient and optimized.
Example Extensions
public static void RotateDown<T>(this IList<T> source)
{
// ideally you would remove first to save a potential capacity expansion
source.Add(source[0]);
source.RemoveAt(0);
}
public static void RotateUp<T>(this IList<T> source)
{
// ideally you would remove first to save a potential capacity expansion
source.Insert(0,source[^1]);
source.RemoveAt(source.Count-1);
}
Test
var list = new List<int>() {1, 2, 3, 4, 5};
Console.WriteLine(string.Join(", ",list));
list.RotateUp();
Console.WriteLine(string.Join(", ", list));
list.RotateUp();
Console.WriteLine(string.Join(", ", list));
list.RotateDown();
Console.WriteLine(string.Join(", ", list));
list.RotateDown();
Console.WriteLine(string.Join(", ", list));
Output
1, 2, 3, 4, 5
5, 1, 2, 3, 4
4, 5, 1, 2, 3
5, 1, 2, 3, 4
1, 2, 3, 4, 5
Note : Methods lack any range checking. Also ideally this would target a less derived interface, though it's only an example.