C# : Extending Generic class

11,129

Solution 1

You can use an extension method:

public static class ExtensionMethods {

    public static User DoMagicFunction(this Repository<User> repository) {
        // some magic
        return null; //or another user
    } 

}

This will thus add the function in a syntactically nice way to Repository<User> objects.

In case you want to support it not only for Users, but for subclasses of Users as well, you can make the function generic:

public static class ExtensionMethods {

    public static TEntity DoMagicFunction<TEntity>(this Repository<TEntity> repository)
        where TEntity : User {
        // some magic
        return null; //or another TEntity
    } 

}

Solution 2

C# has a language feature called Extension Methods, you probably are using them from the .NET framework without knowing (e.g. the linq extensions methods). It's common to extend your classes or even your interfaces with extension methods without breaking the functionality of your code. Here is an example for your case.

Suppose you have a generic IRepository interface:

public interface IRepository<TEntity> where TEntity : class, IEntity
{
    IQueryable<TEntity> Entities { get; }
}

This interface adheres to the SOLID principles, especially the O and I principle.

Now suppose IEntity looks like this:

public interface IEntity
{
    int Id { get; }
}

Now you could perfectly imagine an often reusable extension method like this:

public static class RepositoryExtensions
{
    // similar to your MagicFunction
    public static TEntity GetById<TEntity>(this IRepository<TEntity> repository, int id)
         where TEntity : class, IEntity
    {
        return repository.Entities.Single(entity => entity.Id == id);
    }
}

In a similar manner you could also extend your Repository class

public static class RepositoryExtensions
{
    public static TEntity GenericMagicFunction<TEntity>(this Repository<TEntity> repository)
    {
         //do some stuff
    }
}

You can now consume that like this:

var repository = new Repository<User>();
var user = repository.GenericMagicFunction();

You could also limit your extension method:

public static class RepositoryExtensions
{
    public static User DoMagicFunction(this Repository<User> repository)
    {
         //do some stuff
    }
}

But doing this will defeat it's purpose, you could rather just implement this in the Repository<User> class.

If your system and architecture uses Dependency Injection, you're probably injecting an IRepository<User> to your consuming classes. So the first or second extension method examples I've provided would make the most sense.

Solution 3

Extension method is a best choice for this case.

Note: I have not checked but you should check Dependency Injection still works well as normal.

You can use below code for testing:

public class Employee
{
}

public class User
{
}

public interface IRepo<TEntity> where TEntity : class
{
    TEntity Get(int id);
    DbSet<TEntity> Get(Expression<Func<TEntity, bool>> predicate);
    DbContext GetContext();
}

public class Repo<TEntity> : IRepo<TEntity> where TEntity : class
{
    DbContext _context;
    public TEntity Get(int id)
    {
        return _context.Set<TEntity>()
                       .Find(id);
    }

    public DbSet<TEntity> Get(Expression<Func<TEntity, bool>> predicate)
    {
        return _context.Set<TEntity>();
    }

    public DbContext GetContext()
    {
        return _context;
    }
}

public static class RepoExtensions
{
    public static ChangeTracker DoMagic(this Repo<User> userRepo)
    {
        return userRepo.GetContext().ChangeTracker;
    }
}

public static class Test
{
    public static void DoTest()
    {
        Repo<User> repoUser = new Repo<User>();
        repoUser.DoMagic();

        Repo<Employee> repoEmployee = new Repo<Employee>();
        //repoEmployee.DoMagic();
    }
}

Solution 4

If you want to extend any repository you can do it like this.

public static class RepositoryExtension
{
    public static void MagicMethod<TEntity>(this IRepository<TEntity> repo) where TEntity: class
    {
        ....
    }
}

For a specific repository (eg User repository) you can use a similar process

public static class RepositoryExtension
{
    public static void MagicMethod(this IRepository<User> repo) 
    {
        ....
    }
}

Solution 5

Extension methods are not the way to go, because the code that implements the method can only access public/internal members of the class they extend and you are likely to want your repository's DataContext to be private.

In my opinion, your approach needs to be changed slightly.

What if in the future you want to add a Delete method to your generic repository, but you have some entities that should never be deleted? You'll end up with an instance of a repository for something like PurchaseOrder that you'll either have to remember to never call delete on or you will have to create a descendant of Repository<T> that throws an InvalidOperationException if called. Both of which are poor implementations.

Instead, you should delete your IRepository<T> interface completely. Keep your Repository<T> class, but explicitly define a repository interface for every entity that only has the methods you require.

public class Repository<TKey, TEntity>......
{
  public TEntity Get<TEntity>(TKey key)....
  public void Delete(TEntity instance)....
  ...etc...
}

public interface IPurchaseOrderRepository {
  PurchaseOrder Get(int orderNumber);
  // Note: No delete is exposed
}

MyDependencyInjection.Register<IPurchaseOrderRepository, Repository<PurchaseOrder, int>>();

When you need additional methods on your repository you add them to your IPurchaseOrderRepository and create a descendant of Repository<T>

public interface IPurchaseOrderRepository {
  PurchaseOrder Get(int orderNumber);
  void DoSomethingElse(int orderNumber);
}

public class PurchaseOrderRepository: Repository<PurchaseOrder, int> {
  public void DoSomethingElse(int orderNumber) {.......}
}


MyDependencyInjection.Register<IPurchaseOrderRepository, PurchaseOrderRepository>();
Share:
11,129

Related videos on Youtube

Shekhar Pankaj
Author by

Shekhar Pankaj

Full time : Sketching, Travelling Part time : Learning, Reading, Technology, Codes Don’t worry if it doesn’t work right. If everything did, you’d be out of a job.

Updated on June 19, 2022

Comments

  • Shekhar Pankaj
    Shekhar Pankaj almost 2 years
    partial class Repository<TEntity> : IRepository<TEntity> where TEntity : class
    {
    }
    

    My generic repository implements a common set of methods for TEntity like

    public TEntity Get(int id)
    {
        return _context.Set<TEntity>()
            .Find(id);
    }
    
    public TEntity Get(Expression<Func<TEntity, bool>> predicate)
    {
        return _context.Set<TEntity>()
    }
    

    which I can access like

    Repository<User>().Get();
    

    Many repositories does the same set of operation, so it is beneficial but now I want to extend Repository<User> to support some additional behavior.

    partial class Repository<User> : IRepository<User> 
    {
        public user DoMagicFunction()
        {
        }
    }
    

    so that I can use the repository like

    Repository<User>().DoMagicFunction();
    

    how can I extend the same generic class for Some Tentity to extend new behaviour instead of modifying it.

    I could have done the same like creating another UserRepository to support new feature, but the accessor would become

    UserRepository.DoMagicFunction();
    

    but I want it to be like

    Repository<User>().DoMagicFunction();
    
  • decPL
    decPL over 6 years
    The way you wrote this - what's the point of doing an extention method, not a partial class? I believe OP's problem was creating a method for specific type of TEntity (namely User) only.
  • Janne Matikainen
    Janne Matikainen over 6 years
    Wouldn't it be better to keep the extension generic as well and just constraint TEntity for all the entities that should support this extension?
  • Willem Van Onsem
    Willem Van Onsem over 6 years
    @JanneMatikainen: that what the update is all about :) But it is possible that we use a setter for TUser, in that case, this will not work. So it is not always possible without breaking inheritance principles.
  • Janne Matikainen
    Janne Matikainen over 6 years
    True, but maybe change TUser to TEntity.
  • QuantumHive
    QuantumHive over 6 years
    OP just wants to extend his interface, which can be achieved with a language feature (so as I see it, this is just a technical question). Your answers has a much greater scope that could start a discussion about how to use and apply design patterns, layered architectures and some of the software principles like the SOLID principles. I don't think it's up to us to tell OP his design should be different, some other thread should be started to discuss that. Your answer is subjective and I wouldn't consider it as a good answer.
  • Shekhar Pankaj
    Shekhar Pankaj over 6 years
    Although it is little off topic, thanks for this answer.
  • Peter Morris
    Peter Morris over 6 years
    @QuantumHive My answer objectively explains how to achieve what he wants to do, which is to use a base Repository<T> class and have additional methods on certain repository interfaces. I also went as far as to explain why he should take this approach rather than the other approaches suggested. Your opinion of my answer is subjective, and I wouldn't consider it a good opinion ;-)
  • Peter Morris
    Peter Morris over 6 years
    How would the extension method access the private DataContext member on Repository<T> in order to retrieve data using this approach?
  • Peter Morris
    Peter Morris over 6 years
    @ShekharPankaj It has more information than you need to solve your problem, sure, but it does solve your problem so it's not off-topic. I just thought it would be good to also weigh up your alternative options and then explain why this one is the best :)
  • Peter Morris
    Peter Morris over 6 years
    How would this extension method access the private DataContext member of Repository<T> in order to select the data it needs to do its job?
  • Willem Van Onsem
    Willem Van Onsem over 6 years
    @PeterMorris: you make it internal. Let's be adult coders (like Pythonistas) :)
  • Peter Morris
    Peter Morris over 6 years
    @WillemVanOnsem That violates the open-closed principle.
  • Willem Van Onsem
    Willem Van Onsem over 6 years
    @PeterMorris: yes and somehow Python is the most rapidly growing language where everything is public :). The only employee my company once fired was one that spend 10 weeks implementing 21 design patterns, following all standards for a piece of software that could not be generalized whereas a collegue could write an equal piece of software in 1/10th of the amount of code. Principles are a good thing, given you know when to apply them :). Take for instance the Liskov substitution principe. Works nice on paper, but some parts of Java breach it :)
  • Peter Morris
    Peter Morris over 6 years
    @WillemVanOnsem That's just an "Argumentum ad populum" fallacy. Just because something is popular it doesn't make it correct. Javascript lets you do a hell of a lot of things you can't do in C# and it is obviously an extremely popular language, but that doesn't mean C# should allow them. You should have fired that guy. Nobody should write stuff they don't need. (EDIT) But this guy is obviously trying to write an app complex enough to use repository patterns, so I think we should give him suggestions that use sound OOP principles.
  • Willem Van Onsem
    Willem Van Onsem over 6 years
    @PeterMorris: no it is not. Software design of course should consider principles. But one of the often forgotten principles is generating the "minimum change". internal provides an additional level of protection against making things public.
  • Peter Morris
    Peter Morris over 6 years
  • QuantumHive
    QuantumHive over 6 years
    Why would you want to access a private field?! It's private for a reason ;) If you need something like that, it would implicate a bad design and you would need to reconsider your design. I wouldn't even create an extension method on a concrete service like Repository, but that entirely depends on your architecture of course. I tend to work with architectures that are SOLID by design. But you might work with a poor or legacy architecture. Microsoft also has linq extension methods on interfaces like IEnumerable<T>.
  • QuantumHive
    QuantumHive over 6 years
    @PeterMorris Again, your answer is subjective because you provide an alternative approach for OP's design. I think your design is bad and I know for sure at least one person would agree and that's Martin Fowler. Your idea of using the Repository Pattern is wrong. As it states: A Repository mediates between the domain and data mapping layers.... Your design is mixing business logic and domain logic within the Repository Pattern. Also, again, the scope of good design is way too big for this question and should be started in another thread.
  • Peter Morris
    Peter Morris over 6 years
    You wouldn't want to access a private field - but you would want to access a protected field from a descendent class. I am pleased you think exposing fields to allow external access to utility methods is bad practice and that doing so hints at requiring a redesign. That's why I advised against making the field Internal and redesigning the repository in my answer :)
  • Peter Morris
    Peter Morris over 6 years
    @QuantumHive I didn't add any business logic to the repository pattern at all. I did offer an alternative approach to the OP's question - as you said in another comment Why would you want to access a private field? If you need something like that, it would implicate a bad design and you would need to reconsider your design - Making a private field internal implicates a bad design, whereas making it protected for access via descendent classes is standard OOP practice.
  • QuantumHive
    QuantumHive over 6 years
  • QuantumHive
    QuantumHive over 6 years
    The usefulness of extension methods is exactly on the public members of that class or interface you are extending. I also mentioned in my answer that you would defeat it's purpose if you would create an extension method on the concrete Repository class. I think the GetById example I've provided is a perfectly valid example on how to extend a generic component like the IRepository<T> interface. Let us not continue this discussion in comments, but rather do that in chat.
  • Willem Van Onsem
    Willem Van Onsem almost 5 years
    @AlessandroMuzzi: the first one with User is to demonstrate this for a concrete type, not a type parameter.