Given an index return item from opposite end of list
If the entire goal is absolute performance of the AtBash() request, I would simply reverse the letters at startup. I'm speaking outside the context of this particular program and more towards your library where I am making the assumption that the AtBash() method calls vs. startup ratio is much higher to infinite.

If that's the case and we can eat some startup time, and you really want that per-access performance on AtBash(), simply eat a tiny bit of memory to get there:

static List&lt;string&gt; letters = new List&lt;string&gt; {
    "Aleph", "Beis", "Gimmel", "Daled", "Hei", "Vav", "Zayin", "Chies", "Teis",
    "Yud", "Chof", "Lamed", "Mem", "Nun", "Samech", "Ayin", "Pei", "Tzadik",
    "Kuf", "Reish", "Shin", "Taf"
};
static List&lt;string&gt; lettersReversed = letters.AsEnumerable().Reverse().ToList();
static string AtBash(int index) =&gt; lettersReversed[index];


Compare the IL in the initial accessor, letters[Math.Abs((index + 1) - letters.Count)]:

IL_0000:  ldsfld      UserQuery+Program.letters
IL_0005:  ldarg.0     
IL_0006:  ldc.i4.1    
IL_0007:  add         
IL_0008:  ldsfld      UserQuery+Program.letters
IL_000D:  callvirt    System.Collections.Generic.List&lt;System.String&gt;.get_Count
IL_0012:  sub         
IL_0013:  call        System.Math.Abs
IL_0018:  callvirt    System.Collections.Generic.List&lt;System.String&gt;.get_Item
IL_001D:  ret   


To the improved accessor, letters[letters.Count - (index + 1)]:

IL_0000:  ldsfld      UserQuery+Program.letters
IL_0005:  ldsfld      UserQuery+Program.letters
IL_000A:  callvirt    System.Collections.Generic.List&lt;System.String&gt;.get_Count
IL_000F:  ldarg.0     
IL_0010:  ldc.i4.1    
IL_0011:  add         
IL_0012:  sub         
IL_0013:  callvirt    System.Collections.Generic.List&lt;System.String&gt;.get_Item
IL_0018:  ret    


To the fastest accessor, lettersReversed[index]:

IL_0000:  ldsfld      UserQuery+Program.lettersReversed
IL_0005:  ldarg.0     
IL_0006:  callvirt    System.Collections.Generic.List&lt;System.String&gt;.get_Item
IL_000B:  ret  


Given your very minimal amount of actual memory in the example (which I don't think is likely to increase), I think this is a perfectly reasonable approach for any high-throughput library.

Feel free to benchmark this. The more accesses per startup, the better the payoff. You should already see payoff pretty quickly because sorting an array of a few dozen items is very fast and that's all the time we're adding to get the long-term benefits here. I wouldn't be surprised if your program even with trivial levels of access already performs better with this approach.