C# 如何使用扩展方法将附加数据与现有对象关联?

C# 如何使用扩展方法将附加数据与现有对象关联?,c#,methods,properties,attributes,extension-methods,C#,Methods,Properties,Attributes,Extension Methods,自.NETFramework 3.5以来,开发人员已经能够添加可从任何对象类型的实例调用的扩展方法。但是,扩展属性尚未在C#中实现。与扩展方法不同,扩展属性将涉及存储单个对象的一些额外状态信息 然而,即使对于扩展方法,在某些编程场景中,能够访问在其上调用这些扩展方法的对象的添加/扩展后的信息也是非常有用的 这里是最初的问题:如何在C#中添加扩展属性,或以其他方式在对象上设置扩展数据?该类似乎正是医生所要求的,并且似乎没有其他方法可能引起的内存泄漏担忧。下面是我关于ConditionalWeakT

自.NETFramework 3.5以来,开发人员已经能够添加可从任何对象类型的实例调用的扩展方法。但是,扩展属性尚未在C#中实现。与扩展方法不同,扩展属性将涉及存储单个对象的一些额外状态信息

然而,即使对于扩展方法,在某些编程场景中,能够访问在其上调用这些扩展方法的对象的添加/扩展后的信息也是非常有用的

这里是最初的问题:如何在C#中添加扩展属性,或以其他方式在对象上设置扩展数据?

该类似乎正是医生所要求的,并且似乎没有其他方法可能引起的内存泄漏担忧。下面是我关于ConditionalWeakTable使用的第一个简单包装。我将更好地隐藏它们(使它们成为内部的,名称更模糊),并将其他方法放在它们前面,但这是有效的,对我来说是一个很大的安慰和帮助

(感谢svick、Jeppe Stig Nielsen、Tormod和user2246674帮助我思考这一点。)

公共静态类扩展方法
{
私有静态System.Runtime.CompilerServices.ConditionalWeakTable extendedData=new System.Runtime.CompilerServices.ConditionalWeakTable();
内部静态IDictionary CreateDictionary(对象o){
返回新字典();
}
公共静态void SetExtendedDataValue(此对象为o,字符串名称,对象值){
if(string.IsNullOrWhiteSpace(name))抛出新的ArgumentException(“无效名称”);
name=name.Trim();
IDictionary values=(IDictionary)extendedData.GetValue(o,ExtensionMethods.CreateDictionary);
//如果(值==null)
//extendedData.Add(o,values=newdictionary());//这似乎没有必要!
if(值!=null)
值[名称]=值;
其他的
值。删除(名称);
}
公共静态T GetExtendedDataValue(此对象为o,字符串名称)
{
if(string.IsNullOrWhiteSpace(name))抛出新的ArgumentException(“无效名称”);
name=name.Trim();
IDictionary values=(IDictionary)extendedData.GetValue(o,ExtensionMethods.CreateDictionary);
//如果(values==null)/…这也不正确!
//返回默认值(T);
//否则
if(值。容器(名称))
返回(T)个值[名称];
其他的
返回默认值(T);
}
内部静态对象GetExtendedDataValue(此对象为o,字符串名称)
{
if(string.IsNullOrWhiteSpace(name))抛出新的ArgumentException(“无效名称”);
name=name.Trim();
IDictionary values=(IDictionary)extendedData.GetValue(o,null);
如果(值==null)
返回null;
else if(value.ContainsKey(name))
返回值[名称];
其他的
返回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();
    }
}
//
///一个System.Attribute,也是一个IDictionary,用于将扩展数据添加到
///单个对象,无论其类型如何
/// 
公共类ExtensionDataAttribute:System.Attribute,IDictionary
{
//此集合包装的字典,无法通过System.Attribute和dictionary同时扩展
私有IDictionary数据=新字典();
/// 
///将此扩展数据集合添加到指定对象;只应调用一次
/// 
///要向其添加此扩展数据集合的对象
公共无效添加到(对象o){
System.ComponentModel.TypeDescriptor.AddAttributes(o,this);
}
//以下是对包装字典的封装调用,不需要解释;
//访问ExtensionDataAttribute实例后,只需将其用作IDictionary即可
公共void添加(字符串键、对象值)
{
数据。添加(键、值);
}
公共bool ContainsKey(字符串键)
{
返回数据.ContainsKey(键);
}
公共ICollection密钥
{
获取{return data.Keys;}
}
公共布尔删除(字符串键)
{
返回数据。删除(键);
}
公共bool TryGetValue(字符串键,输出对象值)
{
返回数据.TryGetValue(键,输出值);
}
公共ICollection值
{
获取{return data.Values;}
}
公共对象此[字符串键]
{
得到
{
返回数据[键];
}
设置
{
数据[键]=值;
}
}
公共作废添加(KeyValuePair项)
{
数据。添加(项目);
}
公共空间清除()
{
data.Clear();
}
public bool包含(KeyValuePair项)
{
返回数据。包含(它)
/// <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);
    }

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