To answer your questions:
- Singletons are first-and-foremost objects. As such, all the inheritance stuff applies to them as well. Static classes on the other hand are just containers for static functions (and static state). As static members will not be inherited when subtyping types, they do not benefit from inheritance at all. Interfaces also do not allow specifying static members, so you cannot use them either for static classes.
- “Ability to return derived types” is a direct implication of the fact that singletons are objects. As objects, you can pass them to or return them from methods. This means that methods working with those singleton objects do not actually need to know that they are using singletons. Adding to that, you can use an interface type instead of the concrete singleton type. This allows for loose coupling, and makes the code more robust in general, as you can easily exchange that singleton object by some other object which provides the same functionality (by implementing the interface). So this is a good way to get rid of singletons in the long run and use things like dependency injection as a form of inversion of control.
As for database connections, using singletons to maintain a permanent connection that is reused everywhere does make some kind of sense. However, there is usually a different approach in the .NET framework: That approach involves short-living connections that are only used for the very specific short duration a database connection is actually required. So instead of keeping the connection alive all the time, you create the connection, perform your query, and immediately close the connection.
Of course, permanently opening and closing database connections involves some drawback. To compensate that, the .NET frameworks uses a pooling strategy which keeps a number of connections around to be “created” quickly with a very small overhead. So instead of actually creating a real connection from scratch, you get an existing connection from the connection pool, work with it, and then release it back to the connection pool.
// create a new connection
using (var connection = new SqlConnection())
{
// work with the connection
doSomethingWith(connection);
}
// connection is now automatically closed and returned to the connection pool