The immediate benefit is preventing lock-in to a particular technology, giving you the flexibility to choose the appropriate technology later on.
IMHO, a more important reason is to enforce the Ubiquitous Language. As your application/system evolves, you'll need to query data in multiple ways. The Repository can help you encapsulate complex queries.
With a generic implementation, your consumer code might look like this:
customerRepo = ServiceLocator.Current.Resolve<IRepository<Customer>>();
var matchingCustomers = customerRepo.GetAll().Where(c => <some complex condition here>);
Using a repository, it would look like this:
customerRepo = ServiceLocator.Current.Resolve<ICustomerRepository>();
var matchingCustomers = myRepo.GetCustomersWithOrdersPendingFor(new DateTime(2010, 12, 31));
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The second example explictly states the intent of the query, making it easier/quicker to identify in the future.
However (just to complicate matters), you could use Query Specifications to isolate complex query logic, and use then with a generic Repository to get the job done. See this post to see how it's done.
I usually do the following:
- Create a generic repository (i.e. IRepository<TAggregateRoot>)
- Create repositories for each Aggregate Root, implementing IRepository<TAggregateRoot>
- Add query methods as needed to the appropriate repository
- When the repository gets too bloated, refactor the queries into separate Query Specifications
A final note: I once designed an app that worked in both on-line and off-line mode. The easiest solution was to implement two concrete Repositories (using a common interface), and switch between them when the client was connected/disconnected (another example of switching the persistence technology).