Assuming that your generate moves function is something like:
IEnumerable<Move> getValidSuccessorMoves()
{
if (logic)
yield return new Move(...);
if (otherLogic)
yield return new Move(...);
}
and there is some overhead in generating this (there likely isn't as much as you think there is), you can avoid generating the full list each time by using some logic like follows:
int index = random.Next(MAX_POSSIBLE_MOVES);
List<Move> allMoves = new List<Move>(index);
var moves = getValidSuccessorMoves();
foreach (var move in moves)
{
if (index == 0)
return move;
allMoves.Add(move);
}
index = random.Next(allMoves.Count);
return allMoves[index];
This allows you to short circuit if the RNG picks a good number, and doesn't bias the results. This only really gains you when MAX_POSSIBLE_MOVES is somewhat close to the actual number of moves generated.
However, even with this all said and done, you're likely optimizing too early. Is this really the code that is profiling the worst for you? C# is generally good at lots of short-lived allocations and deallocations (that's what the generational GC is for).