我知道过去围绕这个话题也有人问过类似的问题,但没有一个人用现代的、有效的C#回答了我的担忧。
在我的例子中,我试图为我的类变量实现一个“惰性缓存”,因为我们使用的API使我们能够同时请求特定的变量,所以为了方便起见,我们将它们分组为小字符集(并减少对API的请求数量)。
我正在使用PostSharp通过LocationInterceptionAspect和重载每个缓存属性的getter来实现这一点。我将我的属性添加到我的变量之上,以告知它们使用的是哪个字符集。在我们的程序中使用的第一个变量应该为同一字符集中的其他变量加载值,并告知它们已加载。
例如,假设我有4个相同字符集"TEST_CHARSET"的变量a b c d。如果我使用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);
}发布于 2018-07-02 05:10:04
自定义属性(如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>());
}
}https://stackoverflow.com/questions/51102457
复制相似问题