Изменить переменную класса атрибута во время выполнения с помощью Postsharp - PullRequest
0 голосов
/ 29 июня 2018

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

В моем случае я пытаюсь реализовать «ленивый кеш» для моих переменных класса, потому что API, который мы используем, позволяет нам запрашивать конкретные переменные одновременно, поэтому мы сгруппируем их для удобства (и уменьшить количество запросов к API).

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

Например, допустим, у меня есть 4 переменные a b c d одной и той же кодировки "TEST_CHARSET". Если я сделаю Console.WriteLine(myObject.a), это вызовет API, чтобы получить кодировку "TEST_CHARSET" и заполнить значения других переменных. Как только я вызываю Console.WriteLine(myObject.b), никакие вызовы API не должны выполняться, поскольку значение уже было получено из предыдущего вызова.

Вот MVE:

LazyLoad.cs

[PSerializable]
    [MulticastAttributeUsage(PersistMetaData = true, AllowExternalAssemblies = false)]
    [LinesOfCodeAvoided(50)]
    public sealed class CatalogueLazyLoad : LocationInterceptionAspect
    {
        #region PROPERTIES
        public string Name { get; set; }

        public string Charset { get; set; }

        public CacheType Cache { get; set; }

        public bool Loaded { get; set; } = false;
        #endregion

        public CatalogueLazyLoad(string name, string charset)
        {
            Name = name;
            Charset = charset;
            Cache = CacheType.CACHED;
        }

        private void GetValue(LocationInterceptionArgs args, bool propagate = false)
        {
            var properties = args.Instance.GetType().GetProperties();
            // JSONObject is just an object with string KEY and string VALUE, you can add dummy data here using a Dictionary<string, string>
            IEnumerable<JSONObject> result = API.Methods.GetCharsetData(id, Charset).Result;
            if (result.Count() > 0)
            {
                foreach (PropertyInfo propertyInfo in properties)
                {
                    CatalogueLazyLoad attribute = propertyInfo.GetCustomAttribute<CatalogueLazyLoad>();
                    if (attribute != null && attribute.Charset == Charset)
                    {
                        propertyInfo.SetValue(args.Instance, Convert.ChangeType(result.Where(x => x.Key == attribute.Name).Select(x => x.Value).FirstOrDefault(),
                            propertyInfo.PropertyType, CultureInfo.CurrentCulture), null);
                        if (propagate)
                        {
                            // THIS IS WHERE I AM STUCK, HOW TO SET true to LOADED of OTHERS ATTRIBUTES ??
                            propertyInfo.GetCustomAttribute<CatalogueLazyLoad>().Loaded = true;
                        }
                    }
                }
                args.ProceedGetValue();
            }
        }

        public override sealed void OnGetValue(LocationInterceptionArgs args)
        {
            base.OnGetValue(args);

            switch (Cache)
            {
                case CacheType.CACHED:
                    if (!Loaded)
                    {
                        GetValue(args, true);
                        Loaded = true;
                    }
                    break;
                case CacheType.FORCE_NO_CACHE:
                    GetValue(args);
                    break;
                default:
                    break;
            }
        }
    }

Main.cs

public class Test
    {
        [CatalogueLazyLoad("a", "TEST_CHARSET")]
        public string a { get; set; }

        [CatalogueLazyLoad("b", "TEST_CHARSET")]
        public string b { get; set; }

        [CatalogueLazyLoad("c", "TEST_CHARSET")]
        public string c { get; set; }

        [CatalogueLazyLoad("d", "TEST_CHARSET")]
        public string d { get; set; }
    }

    static void Main()
    {
        Test test = new Test();
        Console.WriteLine(test.a);
        // This should not call the API
        Console.WriteLine(test.b);
    }

1 Ответ

0 голосов
/ 02 июля 2018

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

Существует также экземпляр аспекта, созданного для каждого свойства во время выполнения (это также экземпляр CatalogueLazyLoad). Но к ним нельзя получить доступ через API отражения и такие методы, как propertyInfo.GetCustomAttribute.

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

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

[PSerializable]
[MulticastAttributeUsage(PersistMetaData = true, AllowExternalAssemblies = false)]
[LinesOfCodeAvoided(50)]
// We need to implement IInstanceScopedAspect to introduce and import members
public sealed class CatalogueLazyLoad : LocationInterceptionAspect, IInstanceScopedAspect
{
    public string Name { get; set; }

    public string Charset { get; set; }

    public CacheType Cache { get; set; }

    // Introduce a new property into the target class (only once)
    [IntroduceMember(OverrideAction = MemberOverrideAction.Ignore)]
    public HashSet<string> LoadedCharsets { get; set; }

    // Import the introduced property (it may be introduced by this aspect or another aspect on another property)
    [ImportMember("LoadedCharsets", IsRequired = true, Order = ImportMemberOrder.AfterIntroductions)]
    public Property<HashSet<string>> LoadedCharsetsProperty;

    public CatalogueLazyLoad(string name, string charset)
    {
        Name = name;
        Charset = charset;
        Cache = CacheType.CACHED;
    }

    private void GetValue(LocationInterceptionArgs args, bool propagate = false)
    {
        var properties = args.Instance.GetType().GetProperties();
        // JSONObject is just an object with string KEY and string VALUE, you can add dummy data here using a Dictionary<string, string>
        IEnumerable<JSONObject> result = API.Methods.GetCharsetData(id, Charset).Result;
        if (result.Count() > 0)
        {
            foreach (PropertyInfo propertyInfo in properties)
            {
                CatalogueLazyLoad attribute = propertyInfo.GetCustomAttribute<CatalogueLazyLoad>();
                if (attribute != null && attribute.Charset == Charset)
                {
                    propertyInfo.SetValue(args.Instance,
                                          Convert.ChangeType(result.Where(x => x.Key == attribute.Name).Select(x => x.Value).FirstOrDefault(), propertyInfo.PropertyType, CultureInfo.CurrentCulture),
                                          null);
                }
            }

            if (propagate)
            {
                this.LoadedCharsetsProperty.Get().Add(this.Charset);
            }

            args.ProceedGetValue();
        }
    }

    public override sealed void OnGetValue(LocationInterceptionArgs args)
    {
        base.OnGetValue(args);

        switch (Cache)
        {
            case CacheType.CACHED:
                bool loaded = this.LoadedCharsetsProperty.Get().Contains(this.Charset);
                if (!loaded)
                {
                    GetValue(args, true);
                }
                break;
            case CacheType.FORCE_NO_CACHE:
                GetValue(args);
                break;
            default:
                break;
        }
    }

    public object CreateInstance(AdviceArgs adviceArgs)
    {
        return this.MemberwiseClone();
    }

    public void RuntimeInitializeInstance()
    {
        this.LoadedCharsetsProperty.Set(new HashSet<string>());
    }
}
...