Find all words that can create a pyramid
SRP

You should start by splitting this class into smaller pieces to satisfy the Single Responsibility Priciple. One class should do only one thing and yours is doing at least two:


it reads and writes files
it finds the pyramid words


This means you should:


Create one module for reading and writing words into a file
Let the second module only find pyramid words


The first module could be the DictionaryFile

class DictionaryFile 
{
    public IReadOnlyList&lt;string&gt; LoadDictionary(string filePath)
    {
        // ...
    }

    public void SaveDictionary(IEnumerable&lt;string&gt; words, string fileName)
    {
        // ...
    }
}


Keep the names clear but not too long if this is not necessary.



The second module is the PyramidWordFinder:

class PyramidWordFinder
{
    private readonly HashSet&lt;int&gt; _lengths = new HashSet&lt;int&gt;();

    public PyramidWordFinder(int maxLength)
    {
        var length = 3;
        var i = 1;
        while (length &lt;= maxLength)
        {    
            _lengths.Add(length);
            length = ++i + length + 1;
        }
    }

    public bool IsPyramidWord(string value)
    {
        if (!_lengths.Contains(value.Length)) { return false; }

        var groups = value
            .GroupBy(x =&gt; x.ToString(), StringComparer.OrdinalIgnoreCase)
            .Select(x =&gt; x.Count())
            .OrderBy(x =&gt; x);
        return groups.SequenceEqual(Enumerable.Range(1, groups.Length));
    }       
}


HashSet

Instead of having a list with predefined lengths, you can calculate them based on the max length and save them in a HashSet&lt;int&gt; for faster lookup which is O(1) in comparison to the list where it's O(n).

LINQ

To determine whether a word is a pyramid you can use LINQ where you first group each letter and sort it in the ascending order. Then you check if the count is increasing by one:

var groups = value
    .GroupBy(x =&gt; x.ToString(), StringComparer.OrdinalIgnoreCase)
    .Select(x =&gt; x.Count())
    .OrderBy(x =&gt; x);        
return groups.SequenceEqual(Enumerable.Range(1, groups.Length));


StringComparer

With the StringComparer.OrdinalIgnoreCase you don't need to call ToLower or ToUpper.

IEnumerable


public IList&lt;string&gt; FindPyramidWords(IReadOnlyList&lt;string&gt; wordList)



Don't return a complete list of words if this isn't necessary. Use an iterator instead - by using LINQ or the yield return. What if you wanted just the first three words? You would need to calculate them all anyway. With deferred execution you can stop whereever you want to.

Example

You can now easily find all pyramid words with linq:

var pwf = new PyramidWordFinder(maxLength: 55); // or use constant
var words = new string[] { /* words */ }; // from the file
var pyramidWords = words.Where(pwf.IsPyramidWord).ToList();




I'm aware of the fact that the LINQ solution might not be the fastest one but it's definitely the esiest one. If you don't notice any performance hit then why make it so complex? Premature optimization is the root of all evil.

The point is that you should separate each feature so that you can work on it and test it without affecting the others. If you can focus on one thing at a time you can better optimize it.