The most crucial difference between your first example and the second one is called type safety.
I'm assuming that you are asking the question from the point of view of a statically-type language like C# or Java.
In the version that uses generics, your compiler makes sure you always work with the right types, whereas in the second version (the one that uses a more general type), you’d be expected to check that yourself manually. Also, the compiler will constantly force you to cast your general type (e.g. Entity) to a more specific one (e.g., Customer) to use the services the object provides.
In other words, you have to fight the compiler all the time since it will consistently require that we cast types for it to be able to validate the code we write.
With Generics
The first example uses a type variable T at the interface level. It means that when you ever define a variable for this interface type (or when you implement it), you will also be forced to provide a type argument for T.
For example
IRepository<Customer> custRepo = ...;
That is wherever T appears, it must be replaced by Customer, right?
Once you define the type argument for T to be Customer, it is as if your interface declaration would change, in the eyes of the compiler, to be something like this:
public interface IRepository
{
Customer Get(int key);
IQueryable<Customer> Get();
void Save(Customer obj);
void Delete(Customer obj);
}
Therefore, when you use it, you would be forced by the compiler to respect your type argument:
Customer customer = custRepo.Get(10);
customer.setEmail("lskywalker@gmail.com");
custRepo.Save(customer);
In the example above, all repository methods work only with the Customer type. Therefore I cannot pass an incorrect type (e.g., Employee) to the methods since the compiler will enforce type safety everywhere:
Employee employee = empRepo.Get(10);
custRepo.Save(employee); //Uh oh! Compiler error here
Without Generics
On the other hand, in your second example, all you have done is to decide that you’ll use a more generic type. By doing that you sacrifice type safety in exchange for some flexibility:
For example:
IRepository custRepo = ...;
Entity customer = custRepo.Get(10);
((Customer) customer).setEmail("lskywalker@gmail.com"); //Now you'll need casting
custRepo.Save(customer);
In the case above, you're forced to always cast your Entity to a more usable type like Customer to be able to use what it offers. This casting is an unsafe operation, and it can potentially introduce bugs (if we ever make a mistake in our assumptions about the types we're using).
Also, the repository types do not prevent you from passing the wrong types to the methods, and you can make semantic mistakes:
Entity employee = empRepo.Get(10);
custRepo.Save(employee); //Uh Oh!
If you do it this way, you will probably have to make sure in your CustomerRepo that your entity is actually that of a Customer to prevent a mistake like the one in the last line of the example above.
In other words, you would be manually implementing the kind of type safety that the compiler gives you automatically when you use generics.
This is starting to sound like we are trying to use our statically-typed language as if it was a dynamically-typed one, right? That's why we have to fight the compiler all the way to enforce this style of programming.
About Parametric Polymorphism
Now, you may want to explore the idea that generics is also a form of polymorphism known as parametric polymorphism. You may also want to read this another answer I gave in another question where I cite a great paper on polymorphism that might help you broaden your understanding of the topic beyond just class and interface inheritance.
Dynamically-Type Languages vs Statically-Typed Languages Debate
Now, an interesting conclusion that might help you explore this a bit further is that dynamic languages (e.g. JavaScript, Python, Ruby, etc), where you don't have to do explicit type declarations, actually work like your generic-free example.
Dynamic languages work as if all your variables were of type Object, and they simply allow you to do anything you want with that object to avoid that you have to cast your objects to different types continually. It is the responsibility of the programmer to write tests to make sure the object is always used appropriately.
There has always been a big debate between defenders of statically-typed languages vs those that prefer dynamically-typed languages. This is what we typically call a holly war.
I believe that this is actually a topic that you may want to explore more deeply to understand the fundamental differences between your two examples truly and to learn how the static typing and type safety from generics compares to the high flexibility of just using dynamic types.
My recommendation would be that you read an excellent paper called Dynamically–Typed Languages by Laurence Tratt from Bournemouth University.
Now, in languages that are statically-typed like C#, or Java, we are typically expected to exploit type safety as best as we can. But nothing prevents us from writing the code as you would do in a dynamic language, it is just that the compiler will constantly fight us. That’s the case of your second example.
If that's a way of programming that resonates more with you and your style of working, or if it offers you the flexibility that you seek, then perhaps you should consider using a dynamically-type language. Maybe one that can be combined on top of your current platform (e.g. IronRuby or IronPython) such you can also reuse pre-existing code from your current statically-typed language.