自.NET Framework 3.5以来,开发人员已经能够添加可从任何对象类型的实例调用的扩展方法。但是,扩展属性尚未在C#中实现。与扩展方法不同,扩展属性将涉及为单个对象存储一些额外的状态信息。
但是,即使对于扩展方法,在某些编程方案中,能够访问调用这些扩展方法的对象的后添加/扩展信息也非常有用。
以下是原始问题:如何在C#中添加扩展属性或以其他方式设置对象的扩展数据?
答案 0 :(得分:11)
System.Runtime.CompilerServices.ConditionalWeakTable课程似乎正是医生所要求的,而且似乎并没有引起其他方法可能引起的内存泄漏问题。以下是我使用ConditionalWeakTable的第一个简单包装器。我会更好地隐藏它们(使它们更内部,更加模糊地命名)并在其前面放置其他方法,但这对我来说很有帮助和帮助。
(感谢svick,Jeppe Stig Nielsen,Tormod和user2246674帮助我思考这个问题。)
public static class ExtensionMethods
{
private static System.Runtime.CompilerServices.ConditionalWeakTable<object, object> extendedData = new System.Runtime.CompilerServices.ConditionalWeakTable<object, object>();
internal static IDictionary<string, object> CreateDictionary(object o) {
return new Dictionary<string, object>();
}
public static void SetExtendedDataValue(this object o, string name, object value) {
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Invalid name");
name = name.Trim();
IDictionary<string, object> values = (IDictionary<string, object>)extendedData.GetValue(o, ExtensionMethods.CreateDictionary);
// if (values == null)
// extendedData.Add(o, values = new Dictionary<string, object>()); // This doesn't seem to be necessary!
if (value != null)
values[name] = value;
else
values.Remove(name);
}
public static T GetExtendedDataValue<T>(this object o, string name)
{
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Invalid name");
name = name.Trim();
IDictionary<string, object> values = (IDictionary<string, object>)extendedData.GetValue(o, ExtensionMethods.CreateDictionary);
// if (values == null) // ... nor does this!
// return default(T);
// else
if (values.ContainsKey(name))
return (T)values[name];
else
return default(T);
}
internal static object GetExtendedDataValue(this object o, string name)
{
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Invalid name");
name = name.Trim();
IDictionary<string, object> values = (IDictionary<string, object>)extendedData.GetValue(o, null);
if (values == null)
return null;
else if (values.ContainsKey(name))
return values[name];
else
return null;
}
}
(编辑:出于历史目的,原始答案如下。)
System.ComponentModel.TypeDescriptor.GetAttributes(object)方法公开已添加到指定对象的System.Attribute对象的集合。因此,如果将属性添加到能够存储键值对的对象(但不是结构或枚举),则可以通过扩展方法访问这些对,从而将存储机制隐藏在调用代码之外。这不像扩展属性那么干净,因为不可避免的方法pcall语法,但在某些编程方案中仍然有用。
由于存储数据的对象必须继承自System.Attribute,并且事先不知道需要存储什么类型的数据,因此直接的解决方案是创建一个既继承自System.Attribute又实现IDictionary的类。 。然后可以使用易于使用的扩展方法来包装此类的使用,从而进一步简化扩展数据的存储和检索。
以下是一个此类实现的代码:
/// <summary>
/// A System.Attribute which is also an IDictionary, useful for adding extension data to
/// individual objects, no matter the type
/// </summary>
public class ExtensionDataAttribute : System.Attribute, IDictionary<string, object>
{
// The dictionary wrapped by this collection, which cannot extend by System.Attribute and Dictionary at once
private IDictionary<string, object> data = new Dictionary<string, object>();
/// <summary>
/// Adds this collection of extension data to the specified object; should be called only once
/// </summary>
/// <param name="o">The object to which to add this collection of extension data</param>
public void AddTo(object o) {
System.ComponentModel.TypeDescriptor.AddAttributes(o, this);
}
// Following are encapsulated calls to the wrapped dictionary, which should need no explanation;
// after accessing an ExtensionDataAttribute instance, simply use it as an IDictionary<string, object>
public void Add(string key, object value)
{
data.Add(key, value);
}
public bool ContainsKey(string key)
{
return data.ContainsKey(key);
}
public ICollection<string> Keys
{
get { return data.Keys; }
}
public bool Remove(string key)
{
return data.Remove(key);
}
public bool TryGetValue(string key, out object value)
{
return data.TryGetValue(key, out value);
}
public ICollection<object> Values
{
get { return data.Values; }
}
public object this[string key]
{
get
{
return data[key];
}
set
{
data[key] = value;
}
}
public void Add(KeyValuePair<string, object> item)
{
data.Add(item);
}
public void Clear()
{
data.Clear();
}
public bool Contains(KeyValuePair<string, object> item)
{
return data.Contains(item);
}
public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
{
data.CopyTo(array, arrayIndex);
}
public int Count
{
get { return data.Count; }
}
public bool IsReadOnly
{
get { return data.IsReadOnly; }
}
public bool Remove(KeyValuePair<string, object> item)
{
return data.Remove(item);
}
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
return data.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return data.GetEnumerator();
}
}
...以及一些通用的扩展方法,将它进一步包装起来:
/// <summary>
/// Extension methods for setting and getting extension data for individual objects, no matter the type
/// </summary>
public static class ExtensionDataAttributeExtensions {
public static void SetExtensionDataAttributeValue(this object o, string name, object value)
{
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Invalid name");
foreach (Attribute a in System.ComponentModel.TypeDescriptor.GetAttributes(o))
if (a is ExtensionDataAttribute)
{
((ExtensionDataAttribute)a)[name] = value;
return;
}
ExtensionDataAttribute extensionData = new ExtensionDataAttribute();
extensionData[name] = value;
extensionData.AddTo(o);
}
public static T GetExtensionDataAttributeValue<T>(this object o, string name)
{
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Invalid name");
foreach (Attribute a in System.ComponentModel.TypeDescriptor.GetAttributes(o))
if (a is ExtensionDataAttribute)
return (T)((ExtensionDataAttribute)a)[name];
return default(T);
}
public static object GetExtensionDataAttributeValue(this object o, string name)
{
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Invalid name");
foreach (Attribute a in System.ComponentModel.TypeDescriptor.GetAttributes(o))
if (a is ExtensionDataAttribute)
return ((ExtensionDataAttribute)a)[name];
return null;
}
public static void RemoveExtensionDataAttributeValue(this object o, string name) {
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Invalid name");
foreach (Attribute a in System.ComponentModel.TypeDescriptor.GetAttributes(o))
if (a is ExtensionDataAttribute)
((ExtensionDataAttribute)a).Remove(name);
}
}
...最后,两个自定义扩展方法的例子在现实世界的代码中使用这个想法。一个直接使用ExtensionDataAttribute类(因此更多的是螺母和螺栓),另一个使用上面提供的通用扩展方法:
/// <summary>
/// Extension methods showing samples of using the ExtensionDataAttribute class directly, for use
/// in situations where it is undesirable to include the extension methods provided with that class
/// </summary>
public static class ExtensionMethodsExample1 {
/// <summary>
/// Adds a description to the specified string object
/// </summary>
/// <param name="s">The string to describe</param>
/// <param name="description">The description to set</param>
public static void SetDescription(this string s, string description) {
if (string.IsNullOrWhiteSpace(description))
description = "";
foreach (Attribute a in System.ComponentModel.TypeDescriptor.GetAttributes(s))
if (a is ExtensionDataAttribute) {
((ExtensionDataAttribute)a)["Description"] = description;
return;
}
ExtensionDataAttribute extensionData = new ExtensionDataAttribute();
extensionData["Description"] = description;
extensionData.AddTo(s);
}
/// <summary>
/// Gets the description for the specified string, if it has one;
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
public static string GetDescription(this string s) {
foreach (Attribute a in System.ComponentModel.TypeDescriptor.GetAttributes(s))
if (a is ExtensionDataAttribute) {
ExtensionDataAttribute eda = (ExtensionDataAttribute)a;
if (eda.ContainsKey("Description"))
return eda["Description"].ToString();
else
return "";
}
return "";
}
}
/// <summary>
/// Extension methods encapsulating calls to extension methods provided with the ExtensionDataAttribute
/// class, demonstrating increased ease of implementing one's own extension data
/// </summary>
public static class ExtensionMethodsExample2 {
public static string GetDescription(this string s)
{
return s.GetExtensionDataAttributeValue<string>("Description");
}
public static void SetDescription(this string s, string description)
{
s.SetExtensionDataAttributeValue("Description", description);
}
}
我希望这些想法很有用。一个并不总是具有扩展类的奢侈,并且在某些情况下,如果不必在每个方法调用上组装和传递额外信息,它可能使得扩展方法的设计更加清晰。已经在开发人员的代码库中创建了。
答案 1 :(得分:0)
当然有。您只需要一个可以与数据交互的人员访问的词典。无论何时需要数据,都可以调用TheDataStore [theObject]并返回包含所需属性的值对象。
在抽象层面,这样的属性存储是attached properties在WPF中的工作方式。
答案 2 :(得分:0)
我在想这样的事情:
static class Ext
{
static readonly Dictionary<YourType, int> fooValues = new Dictionary<YourType, int>();
public static int GetFoo(this YourType yt)
{
int value;
fooValues.TryGetValue(yt, out value);
return value;
}
public static void SetFoo(this YourType yt, int value)
{
fooValues[yt] = value;
}
}
当然,这只是一个草图。如果YourType
(或从中派生的任何类)覆盖Equals
和GetHashCode
,您可能希望为fooValues
数据存储提供自定义相等比较器。 Dictionary<,>
不是线程安全的,因此如果需要并行性,请使用另一个集合或使用锁。
这可能是一个问题,每个“扩展”这样的实例永远不会被垃圾收集。不管怎么说,你可以尝试使用弱引用。请参阅下面的svick评论。