none
Generics and base Inheritance issue RRS feed

  • Question

  • I am trying to create a generic repository and return a list of entities. My own entity inherits from a base entity which the repository requires as it's generic T. When I return my newly created list, I can see the return value has entities, but each entity returned is null

    My code looks like this:

    using PushyHedge.Standard.ExtensionMethods;
    using System;
    using System.Collections.Generic;
    namespace ConsoleApp2
    {
        class Program
        {
            static void Main(string[] args)
            {
                var at = new MyRepository<MyEntity>();
                List<MyEntity> myResults = at.GetStuff();
                myResults.ForEach(x =>
                {
                    Console.WriteLine(x.ToString());
                });
                Console.ReadLine();
            }
        }
        public class MyBaseEntity
        {
            public int Id { get; set; }
        }
        public class MyEntity : MyBaseEntity
        {
        }
        public class MyRepository<T> where T : MyBaseEntity
        {
            public List<T> GetStuff()
            {
                List<T> results = new List<T>();
                for (int i = 1; i < 11; i++)
                    results.Add(new MyBaseEntity() { Id = i } as T );
                return results; //each entity here is null
            }
        }
    }

    Friday, July 12, 2019 5:49 PM

Answers

  • I don't really recommend generic repos. They don't actually work out well in practice. But I'll focus on the issue you explicitly mention. 

    The only way for the results to return null is if MyBaseEntity cannot be converted to T and in fact it cannot be. MyBaseEntity is a base type. It is really irrelevant that you put T as a derived type because you are never actually creating an instances of T. You're always creating instances of MyBaseEntity. This class is identical to yours and works the same.

    public class MyRepository
    {
       public List<MyBaseEntity> GetStuff()
       {
          var results = new List<MyBaseEntity>();
          for (var I = 1; I < 11; ++I)
             results.Add(new MyBaseEntity() { Id = I });
          return results;
       }
    }

    See, you're creating instances of MyBaseEntity, not T. Therefore T is irrelevant. You then attempt to cast your instance of MyBaseEntity to T (which is technically supposed to be a derived type) and that will always return null. You cannot use `as` to upcast to a derived type. The only way to do that is to do an explicit cast.

    (T)new MyBaseEntity()

    But this will fail because MyBaseEntity isn't T. Let's get rid of the generics for a second and think of it this way.

    public class MyBase
    {}
    
    public class MyDerived : MyBase
    {}
    
    var instance = new MyBase();
    
    MyDerived derived = instance as MyDerived;

    You cannot convert a base class instance to a derived type simply by casting. Casting requires that the underlying instance actually be of the given type (or a derived type) otherwise it fails. 

    To work around this issue your generic repo must create an instance of T. There is no way around this. So you'll need to modify your constraint to make this work.

    public class MyRepository<T> where T: MyBaseEntity, new()
    {
       public List<T> GetStuff()
       {
          var results = new List<T>();
          for (var I = 1; I < 11; ++I)
             results.Add(new T() { Id = I });
          return results;
       }
    }
    Now you are creating instances of T (which derive from MyBaseEntity) and a cast isn't needed.


    Michael Taylor http://www.michaeltaylorp3.net

    Friday, July 12, 2019 6:40 PM
    Moderator

All replies

  • I don't really recommend generic repos. They don't actually work out well in practice. But I'll focus on the issue you explicitly mention. 

    The only way for the results to return null is if MyBaseEntity cannot be converted to T and in fact it cannot be. MyBaseEntity is a base type. It is really irrelevant that you put T as a derived type because you are never actually creating an instances of T. You're always creating instances of MyBaseEntity. This class is identical to yours and works the same.

    public class MyRepository
    {
       public List<MyBaseEntity> GetStuff()
       {
          var results = new List<MyBaseEntity>();
          for (var I = 1; I < 11; ++I)
             results.Add(new MyBaseEntity() { Id = I });
          return results;
       }
    }

    See, you're creating instances of MyBaseEntity, not T. Therefore T is irrelevant. You then attempt to cast your instance of MyBaseEntity to T (which is technically supposed to be a derived type) and that will always return null. You cannot use `as` to upcast to a derived type. The only way to do that is to do an explicit cast.

    (T)new MyBaseEntity()

    But this will fail because MyBaseEntity isn't T. Let's get rid of the generics for a second and think of it this way.

    public class MyBase
    {}
    
    public class MyDerived : MyBase
    {}
    
    var instance = new MyBase();
    
    MyDerived derived = instance as MyDerived;

    You cannot convert a base class instance to a derived type simply by casting. Casting requires that the underlying instance actually be of the given type (or a derived type) otherwise it fails. 

    To work around this issue your generic repo must create an instance of T. There is no way around this. So you'll need to modify your constraint to make this work.

    public class MyRepository<T> where T: MyBaseEntity, new()
    {
       public List<T> GetStuff()
       {
          var results = new List<T>();
          for (var I = 1; I < 11; ++I)
             results.Add(new T() { Id = I });
          return results;
       }
    }
    Now you are creating instances of T (which derive from MyBaseEntity) and a cast isn't needed.


    Michael Taylor http://www.michaeltaylorp3.net

    Friday, July 12, 2019 6:40 PM
    Moderator
  • The generic repository is limited in scope, it is not flexible and is an anti-pattern. The repository pattern is a domain pattern used in Domain Driven Design. 

    https://martinfowler.com/eaaCatalog/repository.html

    https://blog.sapiensworks.com/post/2012/03/05/The-Generic-Repository-Is-An-Anti-Pattern.aspx

    https://www.ben-morris.com/why-the-generic-repository-is-just-a-lazy-anti-pattern/

    https://programmingwithmosh.com/net/common-mistakes-with-the-repository-pattern/

    The Entity Framework implements the repository pattern. So there is no need to implement another abstraction using the repository pattern above EF. 

    Friday, July 12, 2019 8:21 PM