使用Postsharp在运行时更改属性类变量

时间:2018-06-29 13:04:50

标签: c# runtime postsharp

我知道,过去关于这个主题的类似问题曾被问过类似的问题,但是没有一个问题回答了我对现代C#的关注。

在我的情况下,我正在尝试为我的类变量实现“惰性缓存”,因为我们使用的API使我们能够同时询问特定的变量,因此为了方便起见,我们将它们分组在小字符集中(并且降低对API的请求数量。

我正在使用PostSharp通过LocationInterceptionAspect实现这一目标,并重载了每个缓存属性的getter。我将我的属性添加到变量上方,以判断它们位于哪个字符集。在我们的程序中使用的第一个变量应在同一字符集中加载其他变量的值,并告诉它们已被加载。

例如,假设我有四个具有相同字符集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 :(得分:1)

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