1

I have abstract base class like:

public abstract class CacheValueProviderBase<T> where T : ICacheItem
    {

    protected ConcurrentDictionary<int,T> dataList = new ConcurrentDictionary<int, T>();
        public virtual void Add(T model){ // add code }
        public virtual bool Remove(int id){ //remove code }
        public abstract string getName();
        public abstract void UpdateForceFromDataBase();
        public abstract void UpdateForceFromCacheServer();
        public virtual bool allowForUpdater
        {
            get
            {
                return true;
            }
        }
        public virtual bool beforeUpdate()
        {
            return true;
        }
    }

I have multiple derived classes from this abstract class. The Slider_CacheValueProvider class below is used as an example.

public class Slider_CacheValueProvider : CacheValueProviderBase<Cache_Home_Slider_Model>
    {
        public override string getName()
        {
            return "Slider_Cache";
        }

        public override void UpdateForceFromCacheServer()
        { // updating from cache server
        }

        public override void UpdateForceFromDataBase()
        { // updating from database
        }
    }

Slider cache model:

public class Cache_Home_Slider_Model : ICacheItemID
    {
        public int ID { get; set; }

        public string SlideImage { get; set; }

        public string Link { get; set; }
    }

All cache models depends ID property and implement this interface just for easy crup operation:

public interface ICacheItemID
    {
        int ID { get; set; }
    }

Info: My caching mechanism has 2 steps. The first step is an internal cache. The second step is an external cache server.

I have cache updater. It is updating caches periodically which depends abstract class 'allowForUpdater' property. Firstly, I found all derived classes with this:

public static List<Type> CacheTypeList()
        {
            var type = typeof(CacheValueProviderBase<>);
            return Assembly.GetExecutingAssembly().GetTypes().Where(i => !i.IsAbstract && !i.IsInterface &&
            i.BaseType != null && i.BaseType.IsGenericType && i.BaseType.GetGenericTypeDefinition() == type
            ).ToList();
        }

And iterating like this:

foreach (var item in CacheTypeList())
{
    var cache= getCache(item);
    if(cache.allowForUpdater && cache.beforeUpdate())
    {
        cache.UpdateForceFromCacheServer();
    }
}

And the getCache method:

public static CacheValueProviderBase<ICacheItem> getCache(Type type)
{
    var val = storeList.Find(i => i.Key == type).Value;
    return (CacheValueProviderBase<ICacheItem>)val;
}

storeList is static list and include Slider_CacheValueProvider global on app.

The problem is the getCache method. When I try to cast it, I receive an exception. 'Unable to cast object of type ...' . Slider_CacheValueProvider inherited from base and slider model implement from ICacheItem. What is the problem? Why can't I cast?

Update 1:

Using 'out' keyword to abstract class, getting this error: 'Invalid variance modifier. Only interface and delegate type parameters can be specified as variant'.

So i change the abstract class with interface. Interface is :

public interface ICacheProvider<T> where T : ICacheItemID
    {
        DateTime LastModifiedTime { get; set; }

        void Add(T model);

        bool Remove(int id);

        bool Update(T model);

        T Where(Func<T, bool> expression);

        void Clear();

        int Count(int id);


        IEnumerable<T> GetList();

        void AddList(IEnumerable<T> model);

        void RemoveList(IEnumerable<int> model);

        void RemoveByFunc(Func<KeyValuePair<int, T>, bool> expression);

        IEnumerable<T> WhereList(Func<T, bool> expression);


        string getName();

        void UpdateForceFromDataBase(bool updateCache = true);

        void UpdateForceFromCacheServer();

        bool allowForUpdater { get; }

        bool beforeUpdate();
    }

And current abstract class like this:

public abstract class CacheValueProviderBase<T> : ICacheProvider<T> where T : ICacheItemID

If i change interface to 'out T', getting error on Add,Update,AddList,RemoveByFunc. Error is:

"Invalid variance, The Type parameter 'T' must be contravariantly valid on ICacheProvider.Add(T) (or other method name) 'T' is a covariant. "

Update 2:

I changed my code. I created new interface for updater like this:

public interface ICacheUpdaterImplements
{
        string getName();

        void UpdateForceFromDataBase();

        void UpdateForceFromCacheServer();

        bool allowForUpdater();
}

I get this interface like this:

public static ICacheUpdaterImplements getCacheUpdaterImplements(Type type)
        {
            return (ICacheUpdaterImplements)storeList.Single(i => i.Key == type).Value;
        }

And change updater code like this:

foreach (var item in CacheTypeList())
{
    var updater= getCacheUpdaterImplements(item);
    if(updater.allowForUpdater())
    {
        updater.UpdateForceFromCacheServer();
    }
}

So, I see, I have wrong design. I changed code and resolve problem.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Serkan
  • 25
  • 1
  • 10

2 Answers2

6

Neither of the answers given so far are correct. They are right that the problem is that your type is not covariant, but wrong in the proposed solution, which is illegal and will not compile.

Your example is very complicated, so let's look at a simpler example. If you have:

class Animal {}
class Giraffe : Animal {}
class Tiger : Animal {}

Then this conversion is legal:

IEnumerable<Giraffe> giraffes = new List<Giraffe>() { new Giraffe() };
IEnumerable<Animal> animals = giraffes;

This is a covariant conversion. A covariant conversion is a conversion where the justification for the conversion is "Giraffe is convertible to animal, therefore a sequence of giraffes is convertible to a sequence of animals". That is, a covariant conversion is one where an existing conversion justifies a more complex generic conversion.

However, this conversion is not legal:

IList<Giraffe> giraffes = new List<Giraffe>() { new Giraffe() };
IList<Animal> animals = giraffes;

Why is this conversion not allowed? Because it can be abused! We can now say

animals.Add(new Tiger());

The list of animals is still a list of giraffes. You cannot add a tiger to a list of giraffes. You can add a tiger to a list of animals. Therefore, "list of giraffes" is not a subtype of "list of animals", even though giraffe is a subtype of animal. IEnumerable<T> allows covariance because there is no way to insert a tiger into a sequence of giraffes. IList<T> does not allow covariance because there is a way to abuse it.

C# allows covariant conversions like the one you want under the following circumstances:

  • The generic type arguments involved in the covariant conversion -- that is, the stuff in the <> --- must all be reference types. You cannot, say, convert List<int> to IEnumerable<object> even though int is convertible to object. int is not a reference type.

  • The "outer" generic type that you are converting to must be an interface or a delegate type, not a class or a struct.

  • The interface or delegate must be declared as supporting covariance, and the compiler must be able to check that the declaration is valid and never produces a situation where you can put a tiger into a box that can only hold giraffes.

I do not know offhand how to redo your complicated logic to make it work the way you want. You might want to go with a less complicated solution that does not rely on generics so much.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Your answer is extremely clear. However, the fact that there are so many doubts and questions about these covariant issues make me think that, maybe, the implementation of this feature, including the related error messages, is lacking somehow. – Luca Cremonesi Jan 02 '19 at 16:55
  • 1
    @LucaCremonesi: I completely agree. The documentation on this feature is not great, and the error messages could be a lot better. Both are largely my fault, though I have been trying since the feature was first designed to get a clear explanation of why the design is the way it is searchable on the internet. See https://stackoverflow.com/questions/53185806/problem-understanding-covariance-contravariance-with-generics-in-c-sharp/53185948#53185948 for more thoughts in this area. – Eric Lippert Jan 02 '19 at 17:02
0

Try this implementation, it should get you what you need... Since you are using generics quite extensively this is the way to go - but if I were you I'd give the whole construct of yours a second thought, since I assume you won't be able to call this method (only by using reflection)

public ICacheProvider<T> getCache<T>() where T : ICacheItem
{
    var val = storeList.Single(i => i.Key == typeof(T)).Value;
    return (ICacheProvider<T>)val;
}
Ehssan
  • 739
  • 6
  • 15
  • Unfortunately i dont know T. I have just Type. I have to get casted class from type. – Serkan Dec 29 '18 at 16:09
  • @Serkan I'd start from scratch. Who is using your cache? Do you have public cache service interface that is injected (used) somewhere else? – Ehssan Dec 29 '18 at 16:19
  • @Serkan also, I don't think you really gain anything from using generics, if you don't work with specific generic instances , e.g. ICacheProvider but with ICacheProvider - ICacheProvider doesn't have to be generic if you only use it that way – Ehssan Dec 29 '18 at 16:21
  • Clients using this cache. At the begining, I create ICacheProvider pattern and Slider_CacheValueProvider implement this interface. After this, i am writing same CRUD code again and again to all. So i change the code like above. All cache providers derived from abstract class. It means no more repeatable code for me. ICacheItem interface include just int ID property for easy crud operation on abstract class. Can you give me example how should i use ? – Serkan Dec 29 '18 at 17:17
  • @Serkan If you post the complete code sample (main program that instantiates your caching mechanisms) I'll have a look into it.. What kind of application is this running in? WPF, Console App, ...? – Ehssan Dec 29 '18 at 18:34
  • Web application and using owin. Cache initializing on app_start. Init method like this: `foreach (var item in CacheTypeList()) { var temp = Activator.CreateInstance(item); storeList.Add(new KeyValuePair(item, temp)); item.GetMethod("UpdateForceFromCacheServer").Invoke(temp, null); }` Create instance and invoke update method and add static storeList. And i want to get cache use this code: `(Slider_CacheValueProvider)storeList.Find(i => i.Key == typeof(Slider_CacheValueProvider)).Value;` I am getting cache like this because i know type – Serkan Dec 29 '18 at 20:22
  • In my question, i try to cast from Type in iterating by CacheTypeList(). – Serkan Dec 29 '18 at 20:24