Производный класс от общего абстрактного класса и ошибка приведения? - PullRequest
0 голосов
/ 29 декабря 2018

У меня есть абстрактный базовый класс, например:

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;
        }
    }

У меня есть несколько производных классов от этого абстрактного класса.Класс Slider_CacheValueProvider, приведенный ниже, используется в качестве примера.

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
        }
    }

Модель кэша ползунка:

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

        public string SlideImage { get; set; }

        public string Link { get; set; }
    }

Все модели кэша зависят от свойства идентификатора и реализуют этот интерфейс только для упрощения операции переноса:

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

Информация: мой механизм кэширования имеет 2 этапа.Первый шаг - внутренний кеш.Второй шаг - внешний кеш-сервер.

У меня есть обновление кеша.Периодически обновляется кеш, что зависит от свойства абстрактного класса 'allowForUpdater'.Во-первых, я нашел все производные классы с этим:

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();
        }

И итерацией так:

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

И метод getCache:

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

storeListявляется статическим списком и включает глобальный Slider_CacheValueProvider в приложении.

Проблема заключается в методе getCache.Когда я пытаюсь разыграть его, я получаю исключение.'Невозможно привести объект типа ...'.Slider_CacheValueProvider унаследован от базовой модели, а модель слайдера реализована от ICacheItem.В чем проблема?Почему я не могу разыграть?

Обновление 1:

Использование ключевого слова «out» для абстрагирования класса, получение этой ошибки: «Недопустимый модификатор дисперсии.В качестве варианта можно указать только параметры типа интерфейса и делегата.

Поэтому я изменяю абстрактный класс с помощью интерфейса.Интерфейс:

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();
    }

И текущий абстрактный класс, подобный этому:

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

Если я меняю интерфейс на 'out T', получаю ошибку при Add, Update, AddList, RemoveByFunc.Ошибка:

"Недопустимая дисперсия, параметр типа 'T' должен быть противоположно допустимым для ICacheProvider.Add (T) (или другого имени метода)" T "является ковариантным."

Обновление 2:

Я изменил свой код.Я создал новый интерфейс для программы обновления следующим образом:

public interface ICacheUpdaterImplements
{
        string getName();

        void UpdateForceFromDataBase();

        void UpdateForceFromCacheServer();

        bool allowForUpdater();
}

Я получил этот интерфейс следующим образом:

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

И изменил код программы обновления следующим образом:

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

Итак, я вижу, у меня неправильный дизайн.Я изменил код и решил проблему.

Ответы [ 2 ]

0 голосов
/ 29 декабря 2018

Попробуйте эту реализацию, она должна дать вам то, что вам нужно ... Так как вы достаточно широко используете дженерики, это путь, но на вашем месте я бы подумал над всей вашей конструкцией еще раз, так какЯ предполагаю, что вы не сможете вызвать этот метод (только с помощью отражения)

public ICacheProvider<T> getCache<T>() where T : ICacheItem
{
    var val = storeList.Single(i => i.Key == typeof(T)).Value;
    return (ICacheProvider<T>)val;
}
0 голосов
/ 29 декабря 2018

Ни один из приведенных ответов не является правильным.Они правы в том, что проблема в том, что ваш тип не является ковариантным, а неправильным в предлагаемом решении, которое является недопустимым и не будет компилироваться.

Ваш пример очень сложен, поэтому давайте рассмотрим более простой пример.Если у вас есть:

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

, тогда это преобразование допустимо:

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

Это ковариантное преобразование.Ковариантное преобразование - это преобразование, в котором оправдание преобразования: «Жираф обратим в животных, поэтому последовательность жирафов конвертируется в последовательность животных».То есть ковариантное преобразование - это такое, где существующее преобразование оправдывает более сложное общее преобразование .

Однако это преобразование недопустимо:

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

Почемуэто преобразование не допускается?Потому что этим можно злоупотреблять!Теперь мы можем сказать,

animals.Add(new Tiger());

Список животных все еще является списком жирафов .Вы не можете добавить тигра в список жирафов.Вы можете добавить тигра в список животных.Следовательно, «список жирафов» не является подтипом «списка животных», хотя жираф является подтипом животных.IEnumerable<T> допускает ковариацию , потому что нет способа вставить тигра в последовательность жирафов .IList<T> не допускает ковариацию, потому что есть способ злоупотреблять ею.

C # допускает ковариантные преобразования, подобные тому, который вы хотите, при следующих обстоятельствах:

  • универсальныйаргументы типа, участвующие в ковариантном преобразовании - то есть материал в <> --- должен all быть ссылочными типами.Вы не можете, скажем, конвертировать List<int> в IEnumerable<object>, даже если int конвертируется в object.int не является ссылочным типом.

  • «Внешний» универсальный тип, в который вы преобразуете, должен быть интерфейсом или типом делегата, а не классом или структурой.

  • Интерфейс или делегат должны быть объявлены как поддерживающие ковариации, и компилятор должен иметь возможность проверить, что объявление является действительным, и никогда создает ситуацию, в которой вы можетеположите тигра в ящик, в котором могут содержаться только жирафы.

Я не знаю, как из рук в руки переделать вашу сложную логику, чтобы она работала так, как вы хотите.Возможно, вы захотите пойти с менее сложным решением, которое не так сильно зависит от генериков.

...