Reputation: 25
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.
Upvotes: 1
Views: 1734
Reputation: 759
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;
}
Upvotes: 0
Reputation: 659974
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.
Upvotes: 6