Penso che Larsenal abbia dato la risposta migliore finora, ma mi piacerebbe estenderlo a dare un esempio concreto dal funzionamento del codice a livello aziendale (non in C #, ma in un altro linguaggio basato su ECMAScript). Nel mio codice, posso trattare Classi che sono cose fondamentalmente diverse come se fossero le stesse dal punto di vista del codice client.
Nella mia applicazione, ho un'interfaccia IPaging che definisce funzioni come nextPage (), previousPage (), ecc. Questa interfaccia è implementata da View Classes, ma anche da Classi di controller. Ciò che questo mi permette di fare è avviare l'applicazione con una vista che implementa direttamente IPaging che viene passato alla variabile currentPaging dell'applicazione. Questa vista è già attiva e funzionante mentre i controllori sono caricati con i dati necessari per procedere. Questo rende subito qualcosa di interattivo di fronte agli utenti.
Una volta che hanno superato quella vista, la variabile currentPaging dell'applicazione riceve quindi un'istanza di un controller che implementa IPaging. Quel Controller potrebbe quindi implementare l'IPaging chiamando le stesse funzioni su una Vista di IPaging (che potrebbe avere di nuovo viste di IPaging al suo interno che consentono la navigazione gerarchica nidificata), o potrebbe implementare IPaging in un modo diverso, scambiando pagine di dati con una Vista che non ha nulla a che fare con IPaging. Le scelte sono letteralmente illimitate e posso utilizzare lo stesso framework applicativo con Views o Controllers che sono personalizzati per essere leggermente o molto diversi in base ai requisiti che otteniamo. E i nostri clienti, come la maggior parte dei clienti, sono pazzi, quindi dobbiamo essere il più flessibili possibile.
Non mi sono mai pentito di usare un'interfaccia quando forse ci sono solo una o due implementazioni, ma mi sono pentito di non aver usato un'interfaccia quando ho bisogno di un'implementazione che non erediti dalla stessa classe base e non l'ho usata un'interfaccia. E una volta che i riferimenti a una classe specifica vengono diffusi attraverso la base di codice e fatti riferimento a più progetti, è difficile rimettere il genio nella bottiglia.
Un vantaggio delle interfacce che ottengo nella lingua che uso (che sospetto potessi avere anche in C #) è che ho la possibilità di caricare le implementazioni delle interfacce che utilizzo in runtime da una fonte esterna. L'applicazione di caricamento non ha bisogno di compilare le definizioni di classe delle implementazioni caricate - ha solo bisogno della definizione dell'interfaccia per essere in grado di trasmettere a tale interfaccia. Le interfacce, per definizione, non contengono praticamente alcun codice.