Earlier quoted context omitted.
I've used them in the past to keep interface and implementation separate. It's an easy way to stick an adapter between something concrete and the thing that needs something but doesn't care where it's coming from. So, for example, I could have a IGadgetStore with methods for creating, retrieving, updating, and deleting gadget instances and then I can have a bunch of different classes implementing that interface. An o…
OK that makes sense. As an outsider, the C# code bases I look at seem to do this as standard practice, even if the requirement for different classes never materialises. I guess you get used to looking at it, but it seems (perhaps naively) as wasteful and a bit distracting.
It is.
Nowadays, .NET is usually able to do away with the abstraction cost of such interface abuse luckily, but it remains an additional item you mentally have to deal with, which isn't good.
Single-implementation interfaces are still considered an anti-pattern, and teams that over-abstract and mock everything out when writing unit tests usually just waste time in pursuit of multi-decade old cargo cult. These also often test that modules comprise of specific implementations and not whether they simply satisfy the interface contract, which is terrible. And often turn stateless parts of logic that could have lived on some static class into an interface and an implementation injected with DI, that is then mocked out, instead of just calling methods on a class. More difficult to remove, worse locality of behavior, does not answer the question "if tests are green, are we confident this will work in prod?", sadness all around.
I agree with your sentiment. It's much more practical to write functional and component-level tests with coarser granularity of individual test items, but with more extensive coverage of component inputs. There's a wealth of choices for doing this with little effort (e.g. testcontainers).