Found the answer (or at least AN answer) thanks to other people's comments. I used C#'s HashSet datastructure with the following hash function for boards:
public override int GetHashCode()
{
int hash = 0;
int mul = 1;
foreach (Car c in Cars.Values)
{
hash += (c.P1.X + c.P1.Y * W) * mul;
mul += W * H;
}
return hash;
}
This seems to work fine and give unique hash codes for every board (correct me if I'm wrong), assuming cars are always stored in the same order and P1 represents the car's top-left point.
With this solution, I can now solve Rush Hour boards that require 50 moves in less than 0.5s, with reasonable amounts of memory usage.