Static classes are fine as long as they're used in the right places.
Namely: Methods that are 'leaf' methods (they do not modify state, they merely transform the input somehow). Good examples of this are things like Path.Combine. These sorts of things are useful and make for terser syntax.
The problems I have with statics are numerous:
Firstly, if you have static classes, dependencies are hidden. Consider the following:
public static class ResourceLoader
{
public static void Init(string _rootPath) { ... etc. }
public static void GetResource(string _resourceName) { ... etc. }
public static void Quit() { ... etc. }
}
public static class TextureManager
{
private static Dictionary<string, Texture> m_textures;
public static Init(IEnumerable<GraphicsFormat> _formats)
{
m_textures = new Dictionary<string, Texture>();
foreach(var graphicsFormat in _formats)
{
// do something to create loading classes for all
// supported formats or some other contrived example!
}
}
public static Texture GetTexture(string _path)
{
if(m_textures.ContainsKey(_path))
return m_textures[_path];
// How do we know that ResourceLoader is valid at this point?
var texture = ResourceLoader.LoadResource(_path);
m_textures.Add(_path, texture);
return texture;
}
public static Quit() { ... cleanup code }
}
Looking at TextureManager, you cannot tell what initialisation steps must be carried out by looking at a constructor. You must delve into the class to find its dependencies and initialise things in the correct order. In this case, it needs the ResourceLoader to be initialised before running. Now scale up this dependency nightmare and you can probably guess what will happen. Imagine trying to maintain code where there is no explicit order of initialisation. Contrast this with dependency injection with instances -- in that case the code won't even compile if the dependencies are not fulfilled!
Furthermore, if you use statics that modify state, it's like a house of cards. You never know who has access to what, and the design tends to resemble a spaghetti monster.
Finally, and just as importantly, using statics ties a program to a specific implementation. Static code is the antithesis of designing for testability. Testing code that is riddled with statics is a nightmare. A static call can never be swapped for a test double (unless you use testing frameworks specifically designed to mock out static types), so a static system causes everything that uses it to be an instant integration test.
In short, statics are fine for some things and for small tools or throwaway code I wouldn't discourage their use. However, beyond that, they are a bloody nightmare for maintainability, good design and ease of testing.
Here's a good article on the problems: http://gamearchitect.net/2008/09/13/an-anatomy-of-despair-managers-and-contexts/